@juspay/neurolink 8.5.0 → 8.6.0
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/CHANGELOG.md +17 -0
- package/dist/adapters/providerImageAdapter.d.ts +4 -2
- package/dist/adapters/providerImageAdapter.js +72 -11
- package/dist/config/conversationMemory.d.ts +6 -0
- package/dist/config/conversationMemory.js +14 -0
- package/dist/constants/enums.d.ts +23 -3
- package/dist/constants/enums.js +30 -4
- package/dist/constants/tokens.d.ts +27 -12
- package/dist/constants/tokens.js +46 -12
- package/dist/core/baseProvider.js +6 -2
- package/dist/core/modules/GenerationHandler.js +20 -5
- package/dist/core/modules/MessageBuilder.js +4 -0
- package/dist/core/modules/TelemetryHandler.js +6 -1
- package/dist/lib/adapters/providerImageAdapter.d.ts +4 -2
- package/dist/lib/adapters/providerImageAdapter.js +72 -11
- package/dist/lib/config/conversationMemory.d.ts +6 -0
- package/dist/lib/config/conversationMemory.js +14 -0
- package/dist/lib/constants/enums.d.ts +23 -3
- package/dist/lib/constants/enums.js +30 -4
- package/dist/lib/constants/tokens.d.ts +27 -12
- package/dist/lib/constants/tokens.js +46 -12
- package/dist/lib/core/baseProvider.js +6 -2
- package/dist/lib/core/modules/GenerationHandler.js +20 -5
- package/dist/lib/core/modules/MessageBuilder.js +4 -0
- package/dist/lib/core/modules/TelemetryHandler.js +6 -1
- package/dist/lib/middleware/builtin/guardrails.js +7 -0
- package/dist/lib/models/modelRegistry.js +93 -0
- package/dist/lib/neurolink.js +75 -5
- package/dist/lib/providers/googleAiStudio.d.ts +27 -0
- package/dist/lib/providers/googleAiStudio.js +27 -0
- package/dist/lib/providers/googleVertex.d.ts +35 -0
- package/dist/lib/providers/googleVertex.js +38 -0
- package/dist/lib/telemetry/telemetryService.d.ts +1 -1
- package/dist/lib/telemetry/telemetryService.js +4 -4
- package/dist/lib/types/common.d.ts +5 -0
- package/dist/lib/types/content.d.ts +1 -1
- package/dist/lib/types/generateTypes.d.ts +68 -2
- package/dist/lib/types/multimodal.d.ts +38 -1
- package/dist/lib/types/streamTypes.d.ts +21 -2
- package/dist/lib/utils/messageBuilder.js +70 -8
- package/dist/lib/utils/multimodalOptionsBuilder.d.ts +1 -1
- package/dist/middleware/builtin/guardrails.js +7 -0
- package/dist/models/modelRegistry.js +93 -0
- package/dist/neurolink.js +75 -5
- package/dist/providers/googleAiStudio.d.ts +27 -0
- package/dist/providers/googleAiStudio.js +27 -0
- package/dist/providers/googleVertex.d.ts +35 -0
- package/dist/providers/googleVertex.js +38 -0
- package/dist/telemetry/telemetryService.d.ts +1 -1
- package/dist/telemetry/telemetryService.js +4 -4
- package/dist/types/common.d.ts +5 -0
- package/dist/types/content.d.ts +1 -1
- package/dist/types/generateTypes.d.ts +68 -2
- package/dist/types/multimodal.d.ts +38 -1
- package/dist/types/streamTypes.d.ts +21 -2
- package/dist/utils/messageBuilder.js +70 -8
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Tool } from "ai";
|
|
2
2
|
import type { ValidationSchema, StandardRecord } from "./typeAliases.js";
|
|
3
3
|
import type { AIModelProviderConfig } from "./providers.js";
|
|
4
|
-
import type { Content } from "./content.js";
|
|
4
|
+
import type { Content, ImageWithAltText } from "./content.js";
|
|
5
5
|
import type { AnalyticsData, ToolExecutionEvent, ToolExecutionSummary } from "../types/index.js";
|
|
6
6
|
import { AIProviderName } from "../constants/enums.js";
|
|
7
7
|
import type { TokenUsage } from "./analytics.js";
|
|
@@ -125,7 +125,24 @@ export type StreamOptions = {
|
|
|
125
125
|
input: {
|
|
126
126
|
text: string;
|
|
127
127
|
audio?: AudioInputSpec;
|
|
128
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Images to include in the request.
|
|
130
|
+
* Supports simple image data (Buffer, string) or objects with alt text for accessibility.
|
|
131
|
+
*
|
|
132
|
+
* @example Simple usage
|
|
133
|
+
* ```typescript
|
|
134
|
+
* images: [imageBuffer, "https://example.com/image.jpg"]
|
|
135
|
+
* ```
|
|
136
|
+
*
|
|
137
|
+
* @example With alt text for accessibility
|
|
138
|
+
* ```typescript
|
|
139
|
+
* images: [
|
|
140
|
+
* { data: imageBuffer, altText: "Product screenshot showing main dashboard" },
|
|
141
|
+
* { data: "https://example.com/chart.png", altText: "Sales chart for Q3 2024" }
|
|
142
|
+
* ]
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
images?: Array<Buffer | string | ImageWithAltText>;
|
|
129
146
|
csvFiles?: Array<Buffer | string>;
|
|
130
147
|
pdfFiles?: Array<Buffer | string>;
|
|
131
148
|
files?: Array<Buffer | string>;
|
|
@@ -211,6 +228,8 @@ export type StreamResult = {
|
|
|
211
228
|
totalToolExecutions?: number;
|
|
212
229
|
toolExecutionTime?: number;
|
|
213
230
|
hasToolErrors?: boolean;
|
|
231
|
+
guardrailsBlocked?: boolean;
|
|
232
|
+
error?: string;
|
|
214
233
|
};
|
|
215
234
|
analytics?: AnalyticsData | Promise<AnalyticsData>;
|
|
216
235
|
evaluation?: EvaluationData | Promise<EvaluationData>;
|
|
@@ -3,13 +3,37 @@
|
|
|
3
3
|
* Centralized logic for building message arrays from TextGenerationOptions
|
|
4
4
|
* Enhanced with multimodal support for images
|
|
5
5
|
*/
|
|
6
|
-
import { CONVERSATION_INSTRUCTIONS } from "../config/conversationMemory.js";
|
|
6
|
+
import { CONVERSATION_INSTRUCTIONS, STRUCTURED_OUTPUT_INSTRUCTIONS, } from "../config/conversationMemory.js";
|
|
7
7
|
import { ProviderImageAdapter, MultimodalLogger, } from "../adapters/providerImageAdapter.js";
|
|
8
8
|
import { logger } from "./logger.js";
|
|
9
9
|
import { FileDetector } from "./fileDetector.js";
|
|
10
10
|
import { PDFProcessor } from "./pdfProcessor.js";
|
|
11
11
|
import { request, getGlobalDispatcher, interceptors } from "undici";
|
|
12
12
|
import { readFileSync, existsSync } from "fs";
|
|
13
|
+
/**
|
|
14
|
+
* Type guard to check if an image input has alt text
|
|
15
|
+
*/
|
|
16
|
+
function isImageWithAltText(image) {
|
|
17
|
+
return (typeof image === "object" && !Buffer.isBuffer(image) && "data" in image);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Extract image data from an image input (handles both simple and alt text formats)
|
|
21
|
+
*/
|
|
22
|
+
function extractImageData(image) {
|
|
23
|
+
if (isImageWithAltText(image)) {
|
|
24
|
+
return image.data;
|
|
25
|
+
}
|
|
26
|
+
return image;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extract alt text from an image input if available
|
|
30
|
+
*/
|
|
31
|
+
function extractAltText(image) {
|
|
32
|
+
if (isImageWithAltText(image)) {
|
|
33
|
+
return image.altText;
|
|
34
|
+
}
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
13
37
|
/**
|
|
14
38
|
* Type guard for validating message roles
|
|
15
39
|
*/
|
|
@@ -199,6 +223,15 @@ function formatCSVMetadata(metadata) {
|
|
|
199
223
|
}
|
|
200
224
|
return parts.length > 0 ? `**Metadata**: ${parts.join(" | ")}` : "";
|
|
201
225
|
}
|
|
226
|
+
/**
|
|
227
|
+
* Check if structured output mode should be enabled
|
|
228
|
+
* Structured output is used when a schema is provided with json/structured format
|
|
229
|
+
*/
|
|
230
|
+
function shouldUseStructuredOutput(options) {
|
|
231
|
+
return (!!options.schema &&
|
|
232
|
+
(options.output?.format === "json" ||
|
|
233
|
+
options.output?.format === "structured"));
|
|
234
|
+
}
|
|
202
235
|
/**
|
|
203
236
|
* Build a properly formatted message array for AI providers
|
|
204
237
|
* Combines system prompt, conversation history, and current user prompt
|
|
@@ -215,6 +248,10 @@ export async function buildMessagesArray(options) {
|
|
|
215
248
|
if (hasConversationHistory) {
|
|
216
249
|
systemPrompt = `${systemPrompt.trim()}${CONVERSATION_INSTRUCTIONS}`;
|
|
217
250
|
}
|
|
251
|
+
// Add structured output instructions when schema is provided with json/structured format
|
|
252
|
+
if (shouldUseStructuredOutput(options)) {
|
|
253
|
+
systemPrompt = `${systemPrompt.trim()}${STRUCTURED_OUTPUT_INSTRUCTIONS}`;
|
|
254
|
+
}
|
|
218
255
|
// Add system message if we have one
|
|
219
256
|
if (systemPrompt.trim()) {
|
|
220
257
|
messages.push({
|
|
@@ -473,6 +510,10 @@ export async function buildMultimodalMessagesArray(options, provider, model) {
|
|
|
473
510
|
if (hasConversationHistory) {
|
|
474
511
|
systemPrompt = `${systemPrompt.trim()}${CONVERSATION_INSTRUCTIONS}`;
|
|
475
512
|
}
|
|
513
|
+
// Add structured output instructions when schema is provided with json/structured format
|
|
514
|
+
if (shouldUseStructuredOutput(options)) {
|
|
515
|
+
systemPrompt = `${systemPrompt.trim()}${STRUCTURED_OUTPUT_INSTRUCTIONS}`;
|
|
516
|
+
}
|
|
476
517
|
// Add file handling guidance when multimodal files are present
|
|
477
518
|
const hasCSVFiles = (options.input.csvFiles && options.input.csvFiles.length > 0) ||
|
|
478
519
|
(options.input.files &&
|
|
@@ -622,28 +663,47 @@ async function downloadImageFromUrl(url) {
|
|
|
622
663
|
* - URLs: Downloaded and converted to base64 for Vercel AI SDK compatibility
|
|
623
664
|
* - Local files: Converted to base64 for Vercel AI SDK compatibility
|
|
624
665
|
* - Buffers/Data URIs: Processed normally
|
|
666
|
+
* - Supports alt text for accessibility (included as context in text parts)
|
|
625
667
|
*/
|
|
626
668
|
async function convertSimpleImagesToProviderFormat(text, images, provider, _model) {
|
|
627
669
|
// For Vercel AI SDK, we need to return the content in the standard format
|
|
628
670
|
// The Vercel AI SDK will handle provider-specific formatting internally
|
|
671
|
+
// IMPORTANT: Generate alt text descriptions BEFORE URL downloading to maintain correct image numbering
|
|
672
|
+
// This ensures image numbers match the original order provided by users, even if some URLs fail to download
|
|
673
|
+
const altTextDescriptions = images
|
|
674
|
+
.map((image, idx) => {
|
|
675
|
+
const altText = extractAltText(image);
|
|
676
|
+
return altText ? `[Image ${idx + 1}: ${altText}]` : null;
|
|
677
|
+
})
|
|
678
|
+
.filter(Boolean);
|
|
679
|
+
// Build enhanced text with alt text context for accessibility
|
|
680
|
+
// NOTE: Alt text is appended to the user's prompt as contextual information because most AI providers
|
|
681
|
+
// don't have native alt text fields in their APIs. This approach ensures accessibility metadata
|
|
682
|
+
// is preserved and helps AI models better understand image content.
|
|
683
|
+
const enhancedText = altTextDescriptions.length > 0
|
|
684
|
+
? `${text}\n\nImage descriptions for context: ${altTextDescriptions.join(" ")}`
|
|
685
|
+
: text;
|
|
629
686
|
// Smart auto-detection: separate URLs from actual image data
|
|
687
|
+
// Also track alt text for each image
|
|
630
688
|
const urlImages = [];
|
|
631
689
|
const actualImages = [];
|
|
632
690
|
images.forEach((image, _index) => {
|
|
633
|
-
|
|
691
|
+
const imageData = extractImageData(image);
|
|
692
|
+
const altText = extractAltText(image);
|
|
693
|
+
if (typeof imageData === "string" && isInternetUrl(imageData)) {
|
|
634
694
|
// Internet URL - will be downloaded and converted to base64
|
|
635
|
-
urlImages.push(
|
|
695
|
+
urlImages.push({ url: imageData, altText });
|
|
636
696
|
}
|
|
637
697
|
else {
|
|
638
698
|
// Actual image data (file path, Buffer, data URI) - process for Vercel AI SDK
|
|
639
|
-
actualImages.push(
|
|
699
|
+
actualImages.push({ data: imageData, altText });
|
|
640
700
|
}
|
|
641
701
|
});
|
|
642
702
|
// Download URL images and add to actual images
|
|
643
|
-
for (const url of urlImages) {
|
|
703
|
+
for (const { url, altText } of urlImages) {
|
|
644
704
|
try {
|
|
645
705
|
const downloadedDataUri = await downloadImageFromUrl(url);
|
|
646
|
-
actualImages.push(downloadedDataUri);
|
|
706
|
+
actualImages.push({ data: downloadedDataUri, altText });
|
|
647
707
|
}
|
|
648
708
|
catch (error) {
|
|
649
709
|
MultimodalLogger.logError("URL_DOWNLOAD_FAILED_SKIPPING", error, { url });
|
|
@@ -651,9 +711,11 @@ async function convertSimpleImagesToProviderFormat(text, images, provider, _mode
|
|
|
651
711
|
logger.warn(`Failed to download image from ${url}, skipping: ${error instanceof Error ? error.message : String(error)}`);
|
|
652
712
|
}
|
|
653
713
|
}
|
|
654
|
-
const content = [
|
|
714
|
+
const content = [
|
|
715
|
+
{ type: "text", text: enhancedText },
|
|
716
|
+
];
|
|
655
717
|
// Process all images (including downloaded URLs) for Vercel AI SDK
|
|
656
|
-
actualImages.forEach((image, index) => {
|
|
718
|
+
actualImages.forEach(({ data: image }, index) => {
|
|
657
719
|
try {
|
|
658
720
|
// Vercel AI SDK expects { type: 'image', image: Buffer | string, mimeType?: string }
|
|
659
721
|
// For Vertex AI, we need to include mimeType
|
|
@@ -44,7 +44,7 @@ import type { StreamOptions } from "../types/streamTypes.js";
|
|
|
44
44
|
export declare function buildMultimodalOptions(options: StreamOptions, providerName: string, modelName: string): {
|
|
45
45
|
input: {
|
|
46
46
|
text: string;
|
|
47
|
-
images: (string | Buffer<ArrayBufferLike>)[] | undefined;
|
|
47
|
+
images: (string | Buffer<ArrayBufferLike> | import("../types/multimodal.js").ImageWithAltText)[] | undefined;
|
|
48
48
|
content: import("../types/multimodal.js").Content[] | undefined;
|
|
49
49
|
files: (string | Buffer<ArrayBufferLike>)[] | undefined;
|
|
50
50
|
csvFiles: (string | Buffer<ArrayBufferLike>)[] | undefined;
|
|
@@ -69,8 +69,10 @@ export function createGuardrailsMiddleware(config = {}) {
|
|
|
69
69
|
};
|
|
70
70
|
}
|
|
71
71
|
const { stream, ...rest } = await doStream();
|
|
72
|
+
let hasYieldedChunks = false;
|
|
72
73
|
const transformStream = new TransformStream({
|
|
73
74
|
transform(chunk, controller) {
|
|
75
|
+
hasYieldedChunks = true;
|
|
74
76
|
let filteredChunk = chunk;
|
|
75
77
|
if (typeof filteredChunk === "object" &&
|
|
76
78
|
"textDelta" in filteredChunk) {
|
|
@@ -84,6 +86,11 @@ export function createGuardrailsMiddleware(config = {}) {
|
|
|
84
86
|
}
|
|
85
87
|
controller.enqueue(filteredChunk);
|
|
86
88
|
},
|
|
89
|
+
flush() {
|
|
90
|
+
if (!hasYieldedChunks) {
|
|
91
|
+
logger.warn(`[GuardrailsMiddleware] Stream ended without yielding any chunks`);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
87
94
|
});
|
|
88
95
|
return {
|
|
89
96
|
stream: stream.pipeThrough(transformStream),
|
|
@@ -188,6 +188,99 @@ export const MODEL_REGISTRY = {
|
|
|
188
188
|
category: "general",
|
|
189
189
|
},
|
|
190
190
|
// Anthropic Models
|
|
191
|
+
[AnthropicModels.CLAUDE_OPUS_4_5]: {
|
|
192
|
+
id: AnthropicModels.CLAUDE_OPUS_4_5,
|
|
193
|
+
name: "Claude Opus 4.5",
|
|
194
|
+
provider: AIProviderName.ANTHROPIC,
|
|
195
|
+
description: "Anthropic's most capable model with exceptional reasoning, coding, and multimodal capabilities",
|
|
196
|
+
capabilities: {
|
|
197
|
+
vision: true,
|
|
198
|
+
functionCalling: true,
|
|
199
|
+
codeGeneration: true,
|
|
200
|
+
reasoning: true,
|
|
201
|
+
multimodal: true,
|
|
202
|
+
streaming: true,
|
|
203
|
+
jsonMode: false,
|
|
204
|
+
},
|
|
205
|
+
pricing: {
|
|
206
|
+
inputCostPer1K: 0.015,
|
|
207
|
+
outputCostPer1K: 0.075,
|
|
208
|
+
currency: "USD",
|
|
209
|
+
},
|
|
210
|
+
performance: {
|
|
211
|
+
speed: "medium",
|
|
212
|
+
quality: "high",
|
|
213
|
+
accuracy: "high",
|
|
214
|
+
},
|
|
215
|
+
limits: {
|
|
216
|
+
maxContextTokens: 200000,
|
|
217
|
+
maxOutputTokens: 64000,
|
|
218
|
+
maxRequestsPerMinute: 50,
|
|
219
|
+
},
|
|
220
|
+
useCases: {
|
|
221
|
+
coding: 10,
|
|
222
|
+
creative: 10,
|
|
223
|
+
analysis: 10,
|
|
224
|
+
conversation: 9,
|
|
225
|
+
reasoning: 10,
|
|
226
|
+
translation: 9,
|
|
227
|
+
summarization: 9,
|
|
228
|
+
},
|
|
229
|
+
aliases: [
|
|
230
|
+
"claude-4.5-opus",
|
|
231
|
+
"claude-opus-latest",
|
|
232
|
+
"opus-4.5",
|
|
233
|
+
"anthropic-flagship",
|
|
234
|
+
],
|
|
235
|
+
deprecated: false,
|
|
236
|
+
isLocal: false,
|
|
237
|
+
releaseDate: "2025-11-24",
|
|
238
|
+
category: "reasoning",
|
|
239
|
+
},
|
|
240
|
+
[AnthropicModels.CLAUDE_SONNET_4_5]: {
|
|
241
|
+
id: AnthropicModels.CLAUDE_SONNET_4_5,
|
|
242
|
+
name: "Claude Sonnet 4.5",
|
|
243
|
+
provider: AIProviderName.ANTHROPIC,
|
|
244
|
+
description: "Balanced Claude model with excellent performance across all tasks including vision and reasoning",
|
|
245
|
+
capabilities: {
|
|
246
|
+
vision: true,
|
|
247
|
+
functionCalling: true,
|
|
248
|
+
codeGeneration: true,
|
|
249
|
+
reasoning: true,
|
|
250
|
+
multimodal: true,
|
|
251
|
+
streaming: true,
|
|
252
|
+
jsonMode: false,
|
|
253
|
+
},
|
|
254
|
+
pricing: {
|
|
255
|
+
inputCostPer1K: 0.003,
|
|
256
|
+
outputCostPer1K: 0.015,
|
|
257
|
+
currency: "USD",
|
|
258
|
+
},
|
|
259
|
+
performance: {
|
|
260
|
+
speed: "medium",
|
|
261
|
+
quality: "high",
|
|
262
|
+
accuracy: "high",
|
|
263
|
+
},
|
|
264
|
+
limits: {
|
|
265
|
+
maxContextTokens: 200000,
|
|
266
|
+
maxOutputTokens: 64000,
|
|
267
|
+
maxRequestsPerMinute: 100,
|
|
268
|
+
},
|
|
269
|
+
useCases: {
|
|
270
|
+
coding: 10,
|
|
271
|
+
creative: 9,
|
|
272
|
+
analysis: 9,
|
|
273
|
+
conversation: 9,
|
|
274
|
+
reasoning: 10,
|
|
275
|
+
translation: 8,
|
|
276
|
+
summarization: 8,
|
|
277
|
+
},
|
|
278
|
+
aliases: ["claude-4.5-sonnet", "claude-sonnet-latest", "sonnet-4.5"],
|
|
279
|
+
deprecated: false,
|
|
280
|
+
isLocal: false,
|
|
281
|
+
releaseDate: "2025-09-29",
|
|
282
|
+
category: "coding",
|
|
283
|
+
},
|
|
191
284
|
[AnthropicModels.CLAUDE_4_5_HAIKU]: {
|
|
192
285
|
id: AnthropicModels.CLAUDE_4_5_HAIKU,
|
|
193
286
|
name: "Claude 4.5 Haiku",
|
package/dist/neurolink.js
CHANGED
|
@@ -1998,19 +1998,85 @@ Current user's request: ${currentInput}`;
|
|
|
1998
1998
|
}
|
|
1999
1999
|
}
|
|
2000
2000
|
const { stream: mcpStream, provider: providerName } = await this.createMCPStream(enhancedOptions);
|
|
2001
|
-
// Create a wrapper around the stream that accumulates content
|
|
2002
2001
|
let accumulatedContent = "";
|
|
2002
|
+
let chunkCount = 0;
|
|
2003
|
+
const metadata = {
|
|
2004
|
+
fallbackAttempted: false,
|
|
2005
|
+
guardrailsBlocked: false,
|
|
2006
|
+
error: undefined,
|
|
2007
|
+
};
|
|
2003
2008
|
const processedStream = (async function* (self) {
|
|
2004
2009
|
try {
|
|
2005
2010
|
for await (const chunk of mcpStream) {
|
|
2011
|
+
chunkCount++;
|
|
2006
2012
|
if (chunk &&
|
|
2007
2013
|
"content" in chunk &&
|
|
2008
2014
|
typeof chunk.content === "string") {
|
|
2009
2015
|
accumulatedContent += chunk.content;
|
|
2010
|
-
// Emit chunk event for compatibility
|
|
2011
2016
|
self.emitter.emit("response:chunk", chunk.content);
|
|
2012
2017
|
}
|
|
2013
|
-
yield chunk;
|
|
2018
|
+
yield chunk;
|
|
2019
|
+
}
|
|
2020
|
+
if (chunkCount === 0 && !metadata.fallbackAttempted) {
|
|
2021
|
+
metadata.fallbackAttempted = true;
|
|
2022
|
+
const errorMsg = "Stream completed with 0 chunks (possible guardrails block)";
|
|
2023
|
+
metadata.error = errorMsg;
|
|
2024
|
+
const fallbackRoute = ModelRouter.getFallbackRoute(originalPrompt || enhancedOptions.input.text || "", {
|
|
2025
|
+
provider: providerName,
|
|
2026
|
+
model: enhancedOptions.model || "gpt-4o",
|
|
2027
|
+
reasoning: "primary failed",
|
|
2028
|
+
confidence: 0.5,
|
|
2029
|
+
}, { fallbackStrategy: "auto" });
|
|
2030
|
+
logger.warn("Retrying with fallback provider", {
|
|
2031
|
+
originalProvider: providerName,
|
|
2032
|
+
fallbackProvider: fallbackRoute.provider,
|
|
2033
|
+
reason: errorMsg,
|
|
2034
|
+
});
|
|
2035
|
+
try {
|
|
2036
|
+
const fallbackProvider = await AIProviderFactory.createProvider(fallbackRoute.provider, fallbackRoute.model);
|
|
2037
|
+
// Ensure fallback provider can execute tools
|
|
2038
|
+
fallbackProvider.setupToolExecutor({
|
|
2039
|
+
customTools: self.getCustomTools(),
|
|
2040
|
+
executeTool: self.executeTool.bind(self),
|
|
2041
|
+
}, "NeuroLink.fallbackStream");
|
|
2042
|
+
// Get conversation messages for context (same as primary stream)
|
|
2043
|
+
const conversationMessages = await getConversationMessages(self.conversationMemory, {
|
|
2044
|
+
prompt: enhancedOptions.input.text,
|
|
2045
|
+
context: enhancedOptions.context,
|
|
2046
|
+
});
|
|
2047
|
+
const fallbackResult = await fallbackProvider.stream({
|
|
2048
|
+
...enhancedOptions,
|
|
2049
|
+
model: fallbackRoute.model,
|
|
2050
|
+
conversationMessages,
|
|
2051
|
+
});
|
|
2052
|
+
let fallbackChunkCount = 0;
|
|
2053
|
+
for await (const fallbackChunk of fallbackResult.stream) {
|
|
2054
|
+
fallbackChunkCount++;
|
|
2055
|
+
if (fallbackChunk &&
|
|
2056
|
+
"content" in fallbackChunk &&
|
|
2057
|
+
typeof fallbackChunk.content === "string") {
|
|
2058
|
+
accumulatedContent += fallbackChunk.content;
|
|
2059
|
+
self.emitter.emit("response:chunk", fallbackChunk.content);
|
|
2060
|
+
}
|
|
2061
|
+
yield fallbackChunk;
|
|
2062
|
+
}
|
|
2063
|
+
if (fallbackChunkCount === 0) {
|
|
2064
|
+
throw new Error(`Fallback provider ${fallbackRoute.provider} also returned 0 chunks`);
|
|
2065
|
+
}
|
|
2066
|
+
// Fallback succeeded - likely guardrails blocked primary
|
|
2067
|
+
metadata.guardrailsBlocked = true;
|
|
2068
|
+
}
|
|
2069
|
+
catch (fallbackError) {
|
|
2070
|
+
const fallbackErrorMsg = fallbackError instanceof Error
|
|
2071
|
+
? fallbackError.message
|
|
2072
|
+
: String(fallbackError);
|
|
2073
|
+
metadata.error = `${errorMsg}; Fallback failed: ${fallbackErrorMsg}`;
|
|
2074
|
+
logger.error("Fallback provider failed", {
|
|
2075
|
+
fallbackProvider: fallbackRoute.provider,
|
|
2076
|
+
error: fallbackErrorMsg,
|
|
2077
|
+
});
|
|
2078
|
+
throw fallbackError;
|
|
2079
|
+
}
|
|
2014
2080
|
}
|
|
2015
2081
|
}
|
|
2016
2082
|
finally {
|
|
@@ -2053,7 +2119,7 @@ Current user's request: ${currentInput}`;
|
|
|
2053
2119
|
}
|
|
2054
2120
|
}
|
|
2055
2121
|
})(this);
|
|
2056
|
-
const streamResult = await this.processStreamResult(
|
|
2122
|
+
const streamResult = await this.processStreamResult(processedStream, enhancedOptions, factoryResult);
|
|
2057
2123
|
const responseTime = Date.now() - startTime;
|
|
2058
2124
|
this.emitStreamEndEvents(streamResult);
|
|
2059
2125
|
return this.createStreamResponse(streamResult, processedStream, {
|
|
@@ -2062,7 +2128,9 @@ Current user's request: ${currentInput}`;
|
|
|
2062
2128
|
startTime,
|
|
2063
2129
|
responseTime,
|
|
2064
2130
|
streamId,
|
|
2065
|
-
fallback:
|
|
2131
|
+
fallback: metadata.fallbackAttempted,
|
|
2132
|
+
guardrailsBlocked: metadata.guardrailsBlocked,
|
|
2133
|
+
error: metadata.error,
|
|
2066
2134
|
});
|
|
2067
2135
|
}
|
|
2068
2136
|
catch (error) {
|
|
@@ -2181,6 +2249,8 @@ Current user's request: ${currentInput}`;
|
|
|
2181
2249
|
startTime: config.startTime,
|
|
2182
2250
|
responseTime: config.responseTime,
|
|
2183
2251
|
fallback: config.fallback || false,
|
|
2252
|
+
guardrailsBlocked: config.guardrailsBlocked,
|
|
2253
|
+
error: config.error,
|
|
2184
2254
|
},
|
|
2185
2255
|
};
|
|
2186
2256
|
}
|
|
@@ -6,6 +6,33 @@ import { BaseProvider } from "../core/baseProvider.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* Google AI Studio provider implementation using BaseProvider
|
|
8
8
|
* Migrated from original GoogleAIStudio class to new factory pattern
|
|
9
|
+
*
|
|
10
|
+
* @important Structured Output Limitation
|
|
11
|
+
* Google Gemini models cannot combine function calling (tools) with structured
|
|
12
|
+
* output (JSON schema). When using schemas with output.format: "json", you MUST
|
|
13
|
+
* set disableTools: true.
|
|
14
|
+
*
|
|
15
|
+
* Error without disableTools:
|
|
16
|
+
* "Function calling with a response mime type: 'application/json' is unsupported"
|
|
17
|
+
*
|
|
18
|
+
* This is a Google API limitation documented at:
|
|
19
|
+
* https://ai.google.dev/gemini-api/docs/function-calling
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* // ✅ Correct usage with schemas
|
|
24
|
+
* const provider = new GoogleAIStudioProvider("gemini-2.5-flash");
|
|
25
|
+
* const result = await provider.generate({
|
|
26
|
+
* input: { text: "Analyze data" },
|
|
27
|
+
* schema: MySchema,
|
|
28
|
+
* output: { format: "json" },
|
|
29
|
+
* disableTools: true // Required
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @note Gemini 3 Pro Preview (November 2025) will support combining tools + schemas
|
|
34
|
+
* @note "Too many states for serving" errors can occur with complex schemas + tools.
|
|
35
|
+
* Solution: Simplify schema or use disableTools: true
|
|
9
36
|
*/
|
|
10
37
|
export declare class GoogleAIStudioProvider extends BaseProvider {
|
|
11
38
|
constructor(modelName?: string, sdk?: unknown);
|
|
@@ -27,6 +27,33 @@ if (!process.env.GOOGLE_GENERATIVE_AI_API_KEY &&
|
|
|
27
27
|
/**
|
|
28
28
|
* Google AI Studio provider implementation using BaseProvider
|
|
29
29
|
* Migrated from original GoogleAIStudio class to new factory pattern
|
|
30
|
+
*
|
|
31
|
+
* @important Structured Output Limitation
|
|
32
|
+
* Google Gemini models cannot combine function calling (tools) with structured
|
|
33
|
+
* output (JSON schema). When using schemas with output.format: "json", you MUST
|
|
34
|
+
* set disableTools: true.
|
|
35
|
+
*
|
|
36
|
+
* Error without disableTools:
|
|
37
|
+
* "Function calling with a response mime type: 'application/json' is unsupported"
|
|
38
|
+
*
|
|
39
|
+
* This is a Google API limitation documented at:
|
|
40
|
+
* https://ai.google.dev/gemini-api/docs/function-calling
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* // ✅ Correct usage with schemas
|
|
45
|
+
* const provider = new GoogleAIStudioProvider("gemini-2.5-flash");
|
|
46
|
+
* const result = await provider.generate({
|
|
47
|
+
* input: { text: "Analyze data" },
|
|
48
|
+
* schema: MySchema,
|
|
49
|
+
* output: { format: "json" },
|
|
50
|
+
* disableTools: true // Required
|
|
51
|
+
* });
|
|
52
|
+
* ```
|
|
53
|
+
*
|
|
54
|
+
* @note Gemini 3 Pro Preview (November 2025) will support combining tools + schemas
|
|
55
|
+
* @note "Too many states for serving" errors can occur with complex schemas + tools.
|
|
56
|
+
* Solution: Simplify schema or use disableTools: true
|
|
30
57
|
*/
|
|
31
58
|
export class GoogleAIStudioProvider extends BaseProvider {
|
|
32
59
|
constructor(modelName, sdk) {
|
|
@@ -13,6 +13,41 @@ import { BaseProvider } from "../core/baseProvider.js";
|
|
|
13
13
|
* - Fresh model creation for each request
|
|
14
14
|
* - Enhanced error handling with setup guidance
|
|
15
15
|
* - Tool registration and context management
|
|
16
|
+
*
|
|
17
|
+
* @important Structured Output Limitation (Gemini Models Only)
|
|
18
|
+
* Google Gemini models on Vertex AI cannot combine function calling (tools) with
|
|
19
|
+
* structured output (JSON schema). When using schemas, you MUST set disableTools: true.
|
|
20
|
+
*
|
|
21
|
+
* Error without disableTools:
|
|
22
|
+
* "Function calling with a response mime type: 'application/json' is unsupported"
|
|
23
|
+
*
|
|
24
|
+
* This limitation ONLY affects Gemini models. Anthropic Claude models via Vertex
|
|
25
|
+
* AI do NOT have this limitation and support both tools + schemas simultaneously.
|
|
26
|
+
*
|
|
27
|
+
* @example Gemini models with schemas
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const provider = new GoogleVertexProvider("gemini-2.5-flash");
|
|
30
|
+
* const result = await provider.generate({
|
|
31
|
+
* input: { text: "Analyze data" },
|
|
32
|
+
* schema: MySchema,
|
|
33
|
+
* output: { format: "json" },
|
|
34
|
+
* disableTools: true // Required for Gemini models
|
|
35
|
+
* });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @example Claude models (no limitation)
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const provider = new GoogleVertexProvider("claude-3-5-sonnet-20241022");
|
|
41
|
+
* const result = await provider.generate({
|
|
42
|
+
* input: { text: "Analyze data" },
|
|
43
|
+
* schema: MySchema,
|
|
44
|
+
* output: { format: "json" }
|
|
45
|
+
* // No disableTools needed - Claude supports both
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* @note Gemini 3 Pro Preview (November 2025) will support combining tools + schemas
|
|
50
|
+
* @see https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models
|
|
16
51
|
*/
|
|
17
52
|
export declare class GoogleVertexProvider extends BaseProvider {
|
|
18
53
|
private projectId;
|
|
@@ -234,6 +234,41 @@ const isAnthropicModel = (modelName) => {
|
|
|
234
234
|
* - Fresh model creation for each request
|
|
235
235
|
* - Enhanced error handling with setup guidance
|
|
236
236
|
* - Tool registration and context management
|
|
237
|
+
*
|
|
238
|
+
* @important Structured Output Limitation (Gemini Models Only)
|
|
239
|
+
* Google Gemini models on Vertex AI cannot combine function calling (tools) with
|
|
240
|
+
* structured output (JSON schema). When using schemas, you MUST set disableTools: true.
|
|
241
|
+
*
|
|
242
|
+
* Error without disableTools:
|
|
243
|
+
* "Function calling with a response mime type: 'application/json' is unsupported"
|
|
244
|
+
*
|
|
245
|
+
* This limitation ONLY affects Gemini models. Anthropic Claude models via Vertex
|
|
246
|
+
* AI do NOT have this limitation and support both tools + schemas simultaneously.
|
|
247
|
+
*
|
|
248
|
+
* @example Gemini models with schemas
|
|
249
|
+
* ```typescript
|
|
250
|
+
* const provider = new GoogleVertexProvider("gemini-2.5-flash");
|
|
251
|
+
* const result = await provider.generate({
|
|
252
|
+
* input: { text: "Analyze data" },
|
|
253
|
+
* schema: MySchema,
|
|
254
|
+
* output: { format: "json" },
|
|
255
|
+
* disableTools: true // Required for Gemini models
|
|
256
|
+
* });
|
|
257
|
+
* ```
|
|
258
|
+
*
|
|
259
|
+
* @example Claude models (no limitation)
|
|
260
|
+
* ```typescript
|
|
261
|
+
* const provider = new GoogleVertexProvider("claude-3-5-sonnet-20241022");
|
|
262
|
+
* const result = await provider.generate({
|
|
263
|
+
* input: { text: "Analyze data" },
|
|
264
|
+
* schema: MySchema,
|
|
265
|
+
* output: { format: "json" }
|
|
266
|
+
* // No disableTools needed - Claude supports both
|
|
267
|
+
* });
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* @note Gemini 3 Pro Preview (November 2025) will support combining tools + schemas
|
|
271
|
+
* @see https://cloud.google.com/vertex-ai/docs/generative-ai/learn/models
|
|
237
272
|
*/
|
|
238
273
|
export class GoogleVertexProvider extends BaseProvider {
|
|
239
274
|
projectId;
|
|
@@ -1363,11 +1398,14 @@ export class GoogleVertexProvider extends BaseProvider {
|
|
|
1363
1398
|
getModelSuggestions(requestedModel) {
|
|
1364
1399
|
const availableModels = {
|
|
1365
1400
|
google: [
|
|
1401
|
+
"gemini-3-pro-preview-11-2025",
|
|
1402
|
+
"gemini-3-pro-latest",
|
|
1366
1403
|
"gemini-3-pro-preview",
|
|
1367
1404
|
"gemini-2.5-pro",
|
|
1368
1405
|
"gemini-2.5-flash",
|
|
1369
1406
|
"gemini-2.5-flash-lite",
|
|
1370
1407
|
"gemini-2.0-flash-001",
|
|
1408
|
+
"gemini-2.0-flash-lite",
|
|
1371
1409
|
"gemini-1.5-pro",
|
|
1372
1410
|
"gemini-1.5-flash",
|
|
1373
1411
|
],
|
|
@@ -31,7 +31,7 @@ export declare class TelemetryService {
|
|
|
31
31
|
private initializeTelemetry;
|
|
32
32
|
private initializeMetrics;
|
|
33
33
|
initialize(): Promise<void>;
|
|
34
|
-
traceAIRequest<T>(provider: string, operation: () => Promise<T
|
|
34
|
+
traceAIRequest<T>(provider: string, operation: () => Promise<T>, operationType?: string): Promise<T>;
|
|
35
35
|
recordAIRequest(provider: string, model: string, tokens: number, duration: number): void;
|
|
36
36
|
recordAIError(provider: string, error: Error): void;
|
|
37
37
|
recordMCPToolCall(toolName: string, duration: number, success: boolean): void;
|
|
@@ -108,14 +108,14 @@ export class TelemetryService {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
// AI Operation Tracing (NO-OP when disabled)
|
|
111
|
-
async traceAIRequest(provider, operation) {
|
|
111
|
+
async traceAIRequest(provider, operation, operationType = "generate_text") {
|
|
112
112
|
if (!this.enabled || !this.tracer) {
|
|
113
|
-
return await operation();
|
|
113
|
+
return await operation();
|
|
114
114
|
}
|
|
115
|
-
const span = this.tracer.startSpan(`ai.${provider}
|
|
115
|
+
const span = this.tracer.startSpan(`ai.${provider}.${operationType}`, {
|
|
116
116
|
attributes: {
|
|
117
117
|
"ai.provider": provider,
|
|
118
|
-
"ai.operation":
|
|
118
|
+
"ai.operation": operationType,
|
|
119
119
|
},
|
|
120
120
|
});
|
|
121
121
|
try {
|
package/dist/types/common.d.ts
CHANGED
|
@@ -129,3 +129,8 @@ export type TypedEventEmitter<TEvents extends Record<string, unknown>> = {
|
|
|
129
129
|
listenerCount<K extends keyof TEvents>(event: K): number;
|
|
130
130
|
listeners<K extends keyof TEvents>(event: K): Array<(...args: unknown[]) => void>;
|
|
131
131
|
};
|
|
132
|
+
export type Context = {
|
|
133
|
+
traceName?: string;
|
|
134
|
+
userId?: string;
|
|
135
|
+
sessionId?: string;
|
|
136
|
+
};
|
package/dist/types/content.d.ts
CHANGED
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
* import type { MultimodalInput } from './types/multimodal.js';
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export type { TextContent, ImageContent, CSVContent, PDFContent, AudioContent, VideoContent, Content, MultimodalInput, MultimodalMessage, VisionCapability, ProviderImageFormat, ProcessedImage, ProviderMultimodalPayload, } from "./multimodal.js";
|
|
17
|
+
export type { TextContent, ImageContent, CSVContent, PDFContent, AudioContent, VideoContent, Content, ImageWithAltText, MultimodalInput, MultimodalMessage, VisionCapability, ProviderImageFormat, ProcessedImage, ProviderMultimodalPayload, } from "./multimodal.js";
|
|
18
18
|
export { isTextContent, isImageContent, isCSVContent, isPDFContent, isAudioContent, isVideoContent, isMultimodalInput, } from "./multimodal.js";
|