@juspay/neurolink 7.50.0 → 7.51.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 +7 -0
- package/README.md +12 -9
- package/dist/adapters/providerImageAdapter.js +82 -10
- package/dist/agent/directTools.d.ts +10 -10
- package/dist/agent/directTools.js +5 -3
- package/dist/cli/commands/config.js +1 -0
- package/dist/cli/commands/mcp.js +1 -0
- package/dist/cli/commands/models.js +1 -0
- package/dist/cli/commands/ollama.js +1 -0
- package/dist/cli/commands/setup-anthropic.js +1 -0
- package/dist/cli/commands/setup-azure.js +1 -0
- package/dist/cli/commands/setup-bedrock.js +1 -0
- package/dist/cli/commands/setup-gcp.js +1 -0
- package/dist/cli/commands/setup-google-ai.js +1 -0
- package/dist/cli/commands/setup-huggingface.js +1 -0
- package/dist/cli/commands/setup-mistral.js +1 -0
- package/dist/cli/commands/setup-openai.js +1 -0
- package/dist/cli/commands/setup.js +1 -0
- package/dist/cli/errorHandler.js +1 -0
- package/dist/cli/factories/commandFactory.d.ts +1 -0
- package/dist/cli/factories/commandFactory.js +23 -6
- package/dist/cli/factories/ollamaCommandFactory.js +1 -0
- package/dist/cli/factories/sagemakerCommandFactory.js +1 -0
- package/dist/cli/factories/setupCommandFactory.js +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/loop/conversationSelector.js +1 -0
- package/dist/cli/loop/optionsSchema.js +1 -0
- package/dist/cli/loop/session.js +1 -0
- package/dist/cli/parser.js +1 -0
- package/dist/cli/utils/completeSetup.js +1 -0
- package/dist/cli/utils/envManager.js +1 -0
- package/dist/cli/utils/interactiveSetup.js +1 -0
- package/dist/cli/utils/ollamaUtils.js +1 -0
- package/dist/constants/index.js +1 -1
- package/dist/core/baseProvider.d.ts +5 -0
- package/dist/core/baseProvider.js +70 -20
- package/dist/index.d.ts +3 -3
- package/dist/lib/adapters/providerImageAdapter.js +83 -10
- package/dist/lib/agent/directTools.d.ts +10 -10
- package/dist/lib/agent/directTools.js +6 -3
- package/dist/lib/config/configManager.js +1 -0
- package/dist/lib/config/conversationMemory.js +1 -0
- package/dist/lib/config/taskClassificationConfig.js +1 -0
- package/dist/lib/constants/index.js +2 -1
- package/dist/lib/constants/performance.js +1 -0
- package/dist/lib/constants/retry.js +1 -0
- package/dist/lib/constants/timeouts.js +1 -0
- package/dist/lib/constants/tokens.js +1 -0
- package/dist/lib/core/analytics.js +1 -0
- package/dist/lib/core/baseProvider.d.ts +5 -0
- package/dist/lib/core/baseProvider.js +71 -20
- package/dist/lib/core/constants.js +1 -0
- package/dist/lib/core/conversationMemoryFactory.js +1 -0
- package/dist/lib/core/conversationMemoryInitializer.js +1 -0
- package/dist/lib/core/conversationMemoryManager.js +1 -0
- package/dist/lib/core/dynamicModels.js +1 -0
- package/dist/lib/core/evaluation.js +1 -0
- package/dist/lib/core/evaluationProviders.js +1 -0
- package/dist/lib/core/factory.js +1 -0
- package/dist/lib/core/modelConfiguration.js +1 -0
- package/dist/lib/core/redisConversationMemoryManager.js +1 -0
- package/dist/lib/core/serviceRegistry.js +1 -0
- package/dist/lib/core/streamAnalytics.js +1 -0
- package/dist/lib/evaluation/contextBuilder.js +1 -0
- package/dist/lib/evaluation/index.js +1 -0
- package/dist/lib/evaluation/prompts.js +1 -0
- package/dist/lib/evaluation/ragasEvaluator.js +1 -0
- package/dist/lib/evaluation/retryManager.js +1 -0
- package/dist/lib/evaluation/scoring.js +1 -0
- package/dist/lib/factories/providerFactory.js +1 -0
- package/dist/lib/factories/providerRegistry.js +1 -0
- package/dist/lib/hitl/hitlErrors.js +1 -0
- package/dist/lib/hitl/hitlManager.js +1 -0
- package/dist/lib/hitl/index.js +1 -0
- package/dist/lib/hitl/types.js +1 -0
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +1 -0
- package/dist/lib/mcp/externalServerManager.js +1 -0
- package/dist/lib/mcp/factory.js +1 -0
- package/dist/lib/mcp/flexibleToolValidator.js +1 -0
- package/dist/lib/mcp/index.js +1 -0
- package/dist/lib/mcp/mcpCircuitBreaker.js +1 -0
- package/dist/lib/mcp/mcpClientFactory.js +2 -1
- package/dist/lib/mcp/registry.js +1 -0
- package/dist/lib/mcp/servers/agent/directToolsServer.js +2 -0
- package/dist/lib/mcp/servers/aiProviders/aiAnalysisTools.js +1 -0
- package/dist/lib/mcp/servers/aiProviders/aiCoreServer.js +1 -0
- package/dist/lib/mcp/servers/aiProviders/aiWorkflowTools.js +1 -0
- package/dist/lib/mcp/servers/utilities/utilityServer.js +1 -0
- package/dist/lib/mcp/toolDiscoveryService.js +1 -0
- package/dist/lib/mcp/toolRegistry.js +1 -0
- package/dist/lib/memory/mem0Initializer.js +1 -0
- package/dist/lib/middleware/builtin/analytics.js +1 -0
- package/dist/lib/middleware/builtin/autoEvaluation.js +1 -0
- package/dist/lib/middleware/builtin/guardrails.js +1 -0
- package/dist/lib/middleware/factory.js +1 -0
- package/dist/lib/middleware/index.js +1 -0
- package/dist/lib/middleware/registry.js +1 -0
- package/dist/lib/middleware/utils/guardrailsUtils.js +1 -0
- package/dist/lib/models/modelRegistry.js +1 -0
- package/dist/lib/models/modelResolver.js +2 -0
- package/dist/lib/neurolink.d.ts +6 -0
- package/dist/lib/neurolink.js +135 -5
- package/dist/lib/providers/amazonBedrock.d.ts +1 -0
- package/dist/lib/providers/amazonBedrock.js +166 -14
- package/dist/lib/providers/amazonSagemaker.js +1 -0
- package/dist/lib/providers/anthropic.js +7 -21
- package/dist/lib/providers/anthropicBaseProvider.js +1 -0
- package/dist/lib/providers/azureOpenai.js +5 -21
- package/dist/lib/providers/googleAiStudio.js +5 -21
- package/dist/lib/providers/googleVertex.js +8 -1
- package/dist/lib/providers/huggingFace.js +34 -3
- package/dist/lib/providers/index.js +1 -0
- package/dist/lib/providers/litellm.js +34 -3
- package/dist/lib/providers/mistral.js +32 -2
- package/dist/lib/providers/ollama.d.ts +37 -1
- package/dist/lib/providers/ollama.js +544 -58
- package/dist/lib/providers/openAI.js +5 -21
- package/dist/lib/providers/openaiCompatible.js +41 -4
- package/dist/lib/providers/sagemaker/adaptive-semaphore.js +1 -0
- package/dist/lib/providers/sagemaker/client.js +1 -0
- package/dist/lib/providers/sagemaker/config.js +1 -0
- package/dist/lib/providers/sagemaker/detection.js +1 -0
- package/dist/lib/providers/sagemaker/diagnostics.js +1 -0
- package/dist/lib/providers/sagemaker/error-constants.js +1 -0
- package/dist/lib/providers/sagemaker/errors.js +1 -0
- package/dist/lib/providers/sagemaker/index.js +1 -0
- package/dist/lib/providers/sagemaker/language-model.js +1 -0
- package/dist/lib/providers/sagemaker/parsers.js +1 -0
- package/dist/lib/providers/sagemaker/streaming.js +1 -0
- package/dist/lib/providers/sagemaker/structured-parser.js +1 -0
- package/dist/lib/proxy/awsProxyIntegration.js +1 -0
- package/dist/lib/proxy/proxyFetch.js +1 -0
- package/dist/lib/proxy/utils/noProxyUtils.js +1 -0
- package/dist/lib/sdk/toolRegistration.js +2 -0
- package/dist/lib/services/server/ai/observability/instrumentation.js +1 -0
- package/dist/lib/session/globalSessionState.js +1 -0
- package/dist/lib/telemetry/index.js +1 -0
- package/dist/lib/telemetry/telemetryService.js +1 -0
- package/dist/lib/types/analytics.js +1 -0
- package/dist/lib/types/cli.js +1 -0
- package/dist/lib/types/common.js +1 -0
- package/dist/lib/types/configTypes.js +1 -0
- package/dist/lib/types/content.d.ts +14 -1
- package/dist/lib/types/content.js +1 -0
- package/dist/lib/types/contextTypes.js +1 -0
- package/dist/lib/types/conversation.js +1 -0
- package/dist/lib/types/domainTypes.js +1 -0
- package/dist/lib/types/errors.js +1 -0
- package/dist/lib/types/evaluation.js +1 -0
- package/dist/lib/types/evaluationProviders.js +1 -0
- package/dist/lib/types/evaluationTypes.js +1 -0
- package/dist/lib/types/externalMcp.js +1 -0
- package/dist/lib/types/fileTypes.d.ts +44 -0
- package/dist/lib/types/fileTypes.js +1 -0
- package/dist/lib/types/generateTypes.d.ts +1 -0
- package/dist/lib/types/generateTypes.js +1 -0
- package/dist/lib/types/guardrails.js +1 -0
- package/dist/lib/types/index.js +1 -0
- package/dist/lib/types/mcpTypes.js +1 -0
- package/dist/lib/types/middlewareTypes.js +1 -0
- package/dist/lib/types/modelTypes.js +1 -0
- package/dist/lib/types/observability.js +1 -0
- package/dist/lib/types/providers.d.ts +44 -0
- package/dist/lib/types/providers.js +1 -0
- package/dist/lib/types/sdkTypes.js +1 -0
- package/dist/lib/types/serviceTypes.js +1 -0
- package/dist/lib/types/streamTypes.d.ts +1 -0
- package/dist/lib/types/streamTypes.js +1 -0
- package/dist/lib/types/taskClassificationTypes.js +1 -0
- package/dist/lib/types/tools.js +2 -0
- package/dist/lib/types/typeAliases.js +1 -0
- package/dist/lib/types/universalProviderOptions.js +1 -0
- package/dist/lib/utils/analyticsUtils.js +1 -0
- package/dist/lib/utils/conversationMemory.js +1 -0
- package/dist/lib/utils/conversationMemoryUtils.js +1 -0
- package/dist/lib/utils/csvProcessor.js +1 -0
- package/dist/lib/utils/errorHandling.js +1 -0
- package/dist/lib/utils/evaluationUtils.js +1 -0
- package/dist/lib/utils/factoryProcessing.js +1 -0
- package/dist/lib/utils/fileDetector.js +7 -3
- package/dist/lib/utils/imageProcessor.js +1 -0
- package/dist/lib/utils/logger.js +1 -0
- package/dist/lib/utils/loopUtils.js +1 -0
- package/dist/lib/utils/mcpDefaults.js +1 -0
- package/dist/lib/utils/messageBuilder.js +96 -9
- package/dist/lib/utils/modelRouter.js +1 -0
- package/dist/lib/utils/multimodalOptionsBuilder.d.ts +67 -0
- package/dist/lib/utils/multimodalOptionsBuilder.js +65 -0
- package/dist/lib/utils/optionsConversion.js +1 -0
- package/dist/lib/utils/optionsUtils.js +1 -0
- package/dist/lib/utils/parameterValidation.js +1 -0
- package/dist/lib/utils/pdfProcessor.d.ts +10 -0
- package/dist/lib/utils/pdfProcessor.js +199 -0
- package/dist/lib/utils/performance.js +1 -0
- package/dist/lib/utils/promptRedaction.js +1 -0
- package/dist/lib/utils/providerConfig.js +1 -0
- package/dist/lib/utils/providerHealth.js +1 -0
- package/dist/lib/utils/providerSetupMessages.js +1 -0
- package/dist/lib/utils/providerUtils.js +1 -0
- package/dist/lib/utils/redis.js +1 -0
- package/dist/lib/utils/retryHandler.js +1 -0
- package/dist/lib/utils/schemaConversion.js +1 -0
- package/dist/lib/utils/taskClassificationUtils.js +1 -0
- package/dist/lib/utils/taskClassifier.js +1 -0
- package/dist/lib/utils/timeout.js +1 -0
- package/dist/lib/utils/tokenLimits.js +1 -0
- package/dist/lib/utils/toolUtils.js +1 -0
- package/dist/lib/utils/transformationUtils.js +1 -0
- package/dist/lib/utils/typeUtils.js +1 -0
- package/dist/mcp/mcpClientFactory.js +1 -1
- package/dist/mcp/servers/agent/directToolsServer.js +1 -0
- package/dist/models/modelResolver.js +1 -0
- package/dist/neurolink.d.ts +6 -0
- package/dist/neurolink.js +134 -5
- package/dist/providers/amazonBedrock.d.ts +1 -0
- package/dist/providers/amazonBedrock.js +165 -14
- package/dist/providers/anthropic.js +6 -21
- package/dist/providers/azureOpenai.js +4 -21
- package/dist/providers/googleAiStudio.js +4 -21
- package/dist/providers/googleVertex.js +7 -1
- package/dist/providers/huggingFace.js +33 -3
- package/dist/providers/litellm.js +33 -3
- package/dist/providers/mistral.js +31 -2
- package/dist/providers/ollama.d.ts +37 -1
- package/dist/providers/ollama.js +543 -58
- package/dist/providers/openAI.js +4 -21
- package/dist/providers/openaiCompatible.js +40 -4
- package/dist/sdk/toolRegistration.js +1 -0
- package/dist/types/content.d.ts +14 -1
- package/dist/types/fileTypes.d.ts +44 -0
- package/dist/types/generateTypes.d.ts +1 -0
- package/dist/types/providers.d.ts +44 -0
- package/dist/types/streamTypes.d.ts +1 -0
- package/dist/types/tools.js +1 -0
- package/dist/utils/fileDetector.js +6 -3
- package/dist/utils/messageBuilder.js +95 -9
- package/dist/utils/multimodalOptionsBuilder.d.ts +67 -0
- package/dist/utils/multimodalOptionsBuilder.js +64 -0
- package/dist/utils/pdfProcessor.d.ts +10 -0
- package/dist/utils/pdfProcessor.js +198 -0
- package/package.json +3 -3
|
@@ -3,6 +3,10 @@ import { logger } from "../utils/logger.js";
|
|
|
3
3
|
import { modelConfig } from "../core/modelConfiguration.js";
|
|
4
4
|
import { createProxyFetch } from "../proxy/proxyFetch.js";
|
|
5
5
|
import { TimeoutError } from "../utils/timeout.js";
|
|
6
|
+
import { buildMultimodalMessagesArray } from "../utils/messageBuilder.js";
|
|
7
|
+
import { buildMultimodalOptions } from "../utils/multimodalOptionsBuilder.js";
|
|
8
|
+
import { DEFAULT_MAX_STEPS } from "../core/constants.js";
|
|
9
|
+
import { createAnalytics } from "../core/analytics.js";
|
|
6
10
|
// Model version constants (configurable via environment)
|
|
7
11
|
const DEFAULT_OLLAMA_MODEL = "llama3.1:8b";
|
|
8
12
|
const FALLBACK_OLLAMA_MODEL = "llama3.2:latest"; // Used when primary model fails
|
|
@@ -294,6 +298,62 @@ export class OllamaProvider extends BaseProvider {
|
|
|
294
298
|
});
|
|
295
299
|
return false;
|
|
296
300
|
}
|
|
301
|
+
/**
|
|
302
|
+
* Extract images from multimodal messages for Ollama API
|
|
303
|
+
* Returns array of base64-encoded images
|
|
304
|
+
*/
|
|
305
|
+
extractImagesFromMessages(messages) {
|
|
306
|
+
const images = [];
|
|
307
|
+
for (const msg of messages) {
|
|
308
|
+
if (Array.isArray(msg.content)) {
|
|
309
|
+
for (const content of msg.content) {
|
|
310
|
+
const typedContent = content;
|
|
311
|
+
if (typedContent.type === "image" && typedContent.image) {
|
|
312
|
+
const imageData = typeof typedContent.image === "string"
|
|
313
|
+
? typedContent.image.replace(/^data:image\/\w+;base64,/, "")
|
|
314
|
+
: Buffer.from(typedContent.image).toString("base64");
|
|
315
|
+
images.push(imageData);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return images;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Convert multimodal messages to Ollama chat format
|
|
324
|
+
* Extracts text content and handles images separately
|
|
325
|
+
*/
|
|
326
|
+
convertToOllamaMessages(messages) {
|
|
327
|
+
return messages.map((msg) => {
|
|
328
|
+
let textContent = "";
|
|
329
|
+
const images = [];
|
|
330
|
+
if (typeof msg.content === "string") {
|
|
331
|
+
textContent = msg.content;
|
|
332
|
+
}
|
|
333
|
+
else if (Array.isArray(msg.content)) {
|
|
334
|
+
for (const content of msg.content) {
|
|
335
|
+
const typedContent = content;
|
|
336
|
+
if (typedContent.type === "text" && typedContent.text) {
|
|
337
|
+
textContent += typedContent.text;
|
|
338
|
+
}
|
|
339
|
+
else if (typedContent.type === "image" && typedContent.image) {
|
|
340
|
+
const imageData = typeof typedContent.image === "string"
|
|
341
|
+
? typedContent.image.replace(/^data:image\/\w+;base64,/, "")
|
|
342
|
+
: Buffer.from(typedContent.image).toString("base64");
|
|
343
|
+
images.push(imageData);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const ollamaMsg = {
|
|
348
|
+
role: (msg.role === "system" ? "system" : msg.role),
|
|
349
|
+
content: textContent,
|
|
350
|
+
};
|
|
351
|
+
if (images.length > 0) {
|
|
352
|
+
ollamaMsg.images = images;
|
|
353
|
+
}
|
|
354
|
+
return ollamaMsg;
|
|
355
|
+
});
|
|
356
|
+
}
|
|
297
357
|
// executeGenerate removed - BaseProvider handles all generation with tools
|
|
298
358
|
async executeStream(options, analysisSchema) {
|
|
299
359
|
try {
|
|
@@ -317,52 +377,147 @@ export class OllamaProvider extends BaseProvider {
|
|
|
317
377
|
}
|
|
318
378
|
/**
|
|
319
379
|
* Execute streaming with Ollama's function calling support
|
|
320
|
-
* Uses
|
|
380
|
+
* Uses conversation loop to handle multi-step tool execution
|
|
321
381
|
*/
|
|
322
|
-
async executeStreamWithTools(options,
|
|
382
|
+
async executeStreamWithTools(options, _analysisSchema) {
|
|
383
|
+
const startTime = Date.now();
|
|
384
|
+
const maxIterations = options.maxSteps || DEFAULT_MAX_STEPS;
|
|
385
|
+
let iteration = 0;
|
|
386
|
+
// Get all available tools (direct + MCP + external)
|
|
387
|
+
const allTools = await this.getAllTools();
|
|
323
388
|
// Convert tools to Ollama format
|
|
324
|
-
const ollamaTools = this.convertToolsToOllamaFormat(
|
|
325
|
-
//
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
];
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
temperature: options.temperature,
|
|
342
|
-
max_tokens: options.maxTokens,
|
|
343
|
-
}),
|
|
344
|
-
signal: createAbortSignalWithTimeout(this.timeout),
|
|
345
|
-
});
|
|
346
|
-
if (!response.ok) {
|
|
347
|
-
// Fallback to non-tool mode if chat API fails
|
|
348
|
-
logger.warn("Ollama chat API failed, falling back to generate API", {
|
|
349
|
-
status: response.status,
|
|
350
|
-
statusText: response.statusText,
|
|
389
|
+
const ollamaTools = this.convertToolsToOllamaFormat(allTools);
|
|
390
|
+
// Validate that PDFs are not provided
|
|
391
|
+
if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
|
|
392
|
+
throw new Error("PDF inputs are not supported by OllamaProvider. " +
|
|
393
|
+
"Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.).");
|
|
394
|
+
}
|
|
395
|
+
// Initialize conversation history
|
|
396
|
+
const conversationHistory = [];
|
|
397
|
+
// Build initial messages
|
|
398
|
+
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
399
|
+
options.input?.content?.length ||
|
|
400
|
+
options.input?.files?.length ||
|
|
401
|
+
options.input?.csvFiles?.length);
|
|
402
|
+
if (hasMultimodalInput) {
|
|
403
|
+
logger.debug(`Ollama: Detected multimodal input, using multimodal message builder`, {
|
|
404
|
+
hasImages: !!options.input?.images?.length,
|
|
405
|
+
imageCount: options.input?.images?.length || 0,
|
|
351
406
|
});
|
|
352
|
-
|
|
407
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
408
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
409
|
+
conversationHistory.push(...this.convertToOllamaMessages(multimodalMessages));
|
|
353
410
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
411
|
+
else {
|
|
412
|
+
if (options.systemPrompt) {
|
|
413
|
+
conversationHistory.push({
|
|
414
|
+
role: "system",
|
|
415
|
+
content: options.systemPrompt,
|
|
416
|
+
});
|
|
360
417
|
}
|
|
361
|
-
|
|
418
|
+
conversationHistory.push({
|
|
419
|
+
role: "user",
|
|
420
|
+
content: options.input.text,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
// Conversation loop for multi-step tool execution
|
|
424
|
+
const stream = new ReadableStream({
|
|
425
|
+
start: async (controller) => {
|
|
426
|
+
try {
|
|
427
|
+
while (iteration < maxIterations) {
|
|
428
|
+
logger.debug(`[OllamaProvider] Conversation iteration ${iteration + 1}/${maxIterations}`);
|
|
429
|
+
// Make API request
|
|
430
|
+
const response = await proxyFetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
431
|
+
method: "POST",
|
|
432
|
+
headers: { "Content-Type": "application/json" },
|
|
433
|
+
body: JSON.stringify({
|
|
434
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
435
|
+
messages: conversationHistory,
|
|
436
|
+
tools: ollamaTools,
|
|
437
|
+
tool_choice: "auto",
|
|
438
|
+
stream: true,
|
|
439
|
+
temperature: options.temperature,
|
|
440
|
+
max_tokens: options.maxTokens,
|
|
441
|
+
}),
|
|
442
|
+
signal: createAbortSignalWithTimeout(this.timeout),
|
|
443
|
+
});
|
|
444
|
+
if (!response.ok) {
|
|
445
|
+
throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
|
|
446
|
+
}
|
|
447
|
+
// Process response stream
|
|
448
|
+
const { content, toolCalls, finishReason } = await this.processOllamaResponse(response, controller);
|
|
449
|
+
// Add assistant message to history
|
|
450
|
+
const assistantMessage = {
|
|
451
|
+
role: "assistant",
|
|
452
|
+
content: content || "",
|
|
453
|
+
};
|
|
454
|
+
if (toolCalls && toolCalls.length > 0) {
|
|
455
|
+
assistantMessage.tool_calls = toolCalls;
|
|
456
|
+
}
|
|
457
|
+
conversationHistory.push(assistantMessage);
|
|
458
|
+
// Check finish reason
|
|
459
|
+
if (finishReason === "stop" || !finishReason) {
|
|
460
|
+
// Conversation complete
|
|
461
|
+
controller.close();
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
else if (finishReason === "tool_calls" &&
|
|
465
|
+
toolCalls &&
|
|
466
|
+
toolCalls.length > 0) {
|
|
467
|
+
// Execute tools
|
|
468
|
+
logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tools`);
|
|
469
|
+
const toolResults = await this.executeOllamaTools(toolCalls, options);
|
|
470
|
+
// Add tool results to conversation
|
|
471
|
+
const toolMessage = {
|
|
472
|
+
role: "tool",
|
|
473
|
+
content: JSON.stringify(toolResults),
|
|
474
|
+
};
|
|
475
|
+
conversationHistory.push(toolMessage);
|
|
476
|
+
iteration++;
|
|
477
|
+
continue; // Next iteration
|
|
478
|
+
}
|
|
479
|
+
else if (finishReason === "length") {
|
|
480
|
+
// Max tokens reached, continue conversation
|
|
481
|
+
logger.debug(`[OllamaProvider] Max tokens reached, continuing`);
|
|
482
|
+
conversationHistory.push({
|
|
483
|
+
role: "user",
|
|
484
|
+
content: "Please continue.",
|
|
485
|
+
});
|
|
486
|
+
iteration++;
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
// Unknown finish reason, end conversation
|
|
491
|
+
logger.warn(`[OllamaProvider] Unknown finish reason: ${finishReason}`);
|
|
492
|
+
controller.close();
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
if (iteration >= maxIterations) {
|
|
497
|
+
controller.error(new Error(`Ollama conversation exceeded maximum iterations (${maxIterations})`));
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
catch (error) {
|
|
501
|
+
controller.error(error);
|
|
502
|
+
}
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
// Create analytics promise
|
|
506
|
+
const analyticsPromise = Promise.resolve(createAnalytics(this.providerName, this.modelName || FALLBACK_OLLAMA_MODEL, { usage: { input: 0, output: 0, total: 0 } }, Date.now() - startTime, {
|
|
507
|
+
requestId: `ollama-stream-${Date.now()}`,
|
|
508
|
+
streamingMode: true,
|
|
509
|
+
iterations: iteration,
|
|
510
|
+
note: "Token usage not available from Ollama streaming responses",
|
|
511
|
+
}));
|
|
362
512
|
return {
|
|
363
|
-
stream:
|
|
364
|
-
provider:
|
|
365
|
-
model:
|
|
513
|
+
stream: this.convertToAsyncIterable(stream),
|
|
514
|
+
provider: this.providerName,
|
|
515
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
516
|
+
analytics: analyticsPromise,
|
|
517
|
+
metadata: {
|
|
518
|
+
startTime,
|
|
519
|
+
streamId: `ollama-${Date.now()}`,
|
|
520
|
+
},
|
|
366
521
|
};
|
|
367
522
|
}
|
|
368
523
|
/**
|
|
@@ -370,19 +525,49 @@ export class OllamaProvider extends BaseProvider {
|
|
|
370
525
|
* Fallback for non-tool scenarios or when chat API is unavailable
|
|
371
526
|
*/
|
|
372
527
|
async executeStreamWithoutTools(options, _analysisSchema) {
|
|
528
|
+
// Validate that PDFs are not provided
|
|
529
|
+
if (options.input?.pdfFiles && options.input.pdfFiles.length > 0) {
|
|
530
|
+
throw new Error("PDF inputs are not supported by OllamaProvider. " +
|
|
531
|
+
"Please remove PDFs or use a supported provider (OpenAI, Anthropic, Google Vertex AI, etc.).");
|
|
532
|
+
}
|
|
533
|
+
// Check for multimodal input
|
|
534
|
+
const hasMultimodalInput = !!(options.input?.images?.length ||
|
|
535
|
+
options.input?.content?.length ||
|
|
536
|
+
options.input?.files?.length ||
|
|
537
|
+
options.input?.csvFiles?.length);
|
|
538
|
+
let prompt = options.input.text;
|
|
539
|
+
let images;
|
|
540
|
+
if (hasMultimodalInput) {
|
|
541
|
+
logger.debug(`Ollama (generate API): Detected multimodal input`, {
|
|
542
|
+
hasImages: !!options.input?.images?.length,
|
|
543
|
+
imageCount: options.input?.images?.length || 0,
|
|
544
|
+
});
|
|
545
|
+
const multimodalOptions = buildMultimodalOptions(options, this.providerName, this.modelName);
|
|
546
|
+
const multimodalMessages = await buildMultimodalMessagesArray(multimodalOptions, this.providerName, this.modelName);
|
|
547
|
+
// Extract text from messages for prompt
|
|
548
|
+
prompt = multimodalMessages
|
|
549
|
+
.map((msg) => (typeof msg.content === "string" ? msg.content : ""))
|
|
550
|
+
.join("\n");
|
|
551
|
+
// Extract images
|
|
552
|
+
images = this.extractImagesFromMessages(multimodalMessages);
|
|
553
|
+
}
|
|
554
|
+
const requestBody = {
|
|
555
|
+
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
556
|
+
prompt,
|
|
557
|
+
system: options.systemPrompt,
|
|
558
|
+
stream: true,
|
|
559
|
+
options: {
|
|
560
|
+
temperature: options.temperature,
|
|
561
|
+
num_predict: options.maxTokens,
|
|
562
|
+
},
|
|
563
|
+
};
|
|
564
|
+
if (images && images.length > 0) {
|
|
565
|
+
requestBody.images = images;
|
|
566
|
+
}
|
|
373
567
|
const response = await proxyFetch(`${this.baseUrl}/api/generate`, {
|
|
374
568
|
method: "POST",
|
|
375
569
|
headers: { "Content-Type": "application/json" },
|
|
376
|
-
body: JSON.stringify(
|
|
377
|
-
model: this.modelName || FALLBACK_OLLAMA_MODEL,
|
|
378
|
-
prompt: options.input.text,
|
|
379
|
-
system: options.systemPrompt,
|
|
380
|
-
stream: true,
|
|
381
|
-
options: {
|
|
382
|
-
temperature: options.temperature,
|
|
383
|
-
num_predict: options.maxTokens,
|
|
384
|
-
},
|
|
385
|
-
}),
|
|
570
|
+
body: JSON.stringify(requestBody),
|
|
386
571
|
signal: createAbortSignalWithTimeout(this.timeout),
|
|
387
572
|
});
|
|
388
573
|
if (!response.ok) {
|
|
@@ -424,6 +609,101 @@ export class OllamaProvider extends BaseProvider {
|
|
|
424
609
|
},
|
|
425
610
|
}));
|
|
426
611
|
}
|
|
612
|
+
/**
|
|
613
|
+
* Parse tool calls from Ollama API response
|
|
614
|
+
*/
|
|
615
|
+
parseToolCalls(rawToolCalls) {
|
|
616
|
+
if (!Array.isArray(rawToolCalls)) {
|
|
617
|
+
return [];
|
|
618
|
+
}
|
|
619
|
+
return rawToolCalls
|
|
620
|
+
.map((call) => {
|
|
621
|
+
const callObj = call;
|
|
622
|
+
if (!callObj.function?.name) {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
return {
|
|
626
|
+
id: callObj.id ||
|
|
627
|
+
`tool_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`,
|
|
628
|
+
type: "function",
|
|
629
|
+
function: {
|
|
630
|
+
name: callObj.function.name,
|
|
631
|
+
arguments: callObj.function.arguments || "{}",
|
|
632
|
+
},
|
|
633
|
+
};
|
|
634
|
+
})
|
|
635
|
+
.filter((call) => call !== null);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Process Ollama streaming response and stream content to controller
|
|
639
|
+
* Returns aggregated content, tool calls, and finish reason
|
|
640
|
+
*/
|
|
641
|
+
async processOllamaResponse(response, controller) {
|
|
642
|
+
const reader = response.body?.getReader();
|
|
643
|
+
if (!reader) {
|
|
644
|
+
throw new Error("No response body from Ollama");
|
|
645
|
+
}
|
|
646
|
+
const decoder = new TextDecoder();
|
|
647
|
+
let buffer = "";
|
|
648
|
+
let aggregatedContent = "";
|
|
649
|
+
let aggregatedToolCalls = [];
|
|
650
|
+
let finalFinishReason;
|
|
651
|
+
try {
|
|
652
|
+
while (true) {
|
|
653
|
+
const { done, value } = await reader.read();
|
|
654
|
+
if (done) {
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
buffer += decoder.decode(value, { stream: true });
|
|
658
|
+
const lines = buffer.split("\n");
|
|
659
|
+
buffer = lines.pop() || "";
|
|
660
|
+
for (const line of lines) {
|
|
661
|
+
if (line.trim() && line.startsWith("data: ")) {
|
|
662
|
+
const dataLine = line.slice(6); // Remove "data: " prefix
|
|
663
|
+
if (dataLine === "[DONE]") {
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
try {
|
|
667
|
+
const parsed = JSON.parse(dataLine);
|
|
668
|
+
const processed = this.processOllamaStreamData(parsed);
|
|
669
|
+
if (!processed) {
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
// Stream content to controller
|
|
673
|
+
if (processed.content) {
|
|
674
|
+
aggregatedContent += processed.content;
|
|
675
|
+
controller.enqueue({
|
|
676
|
+
content: processed.content,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
// Collect tool calls
|
|
680
|
+
if (processed.toolCalls && processed.toolCalls.length > 0) {
|
|
681
|
+
aggregatedToolCalls = [
|
|
682
|
+
...aggregatedToolCalls,
|
|
683
|
+
...processed.toolCalls,
|
|
684
|
+
];
|
|
685
|
+
}
|
|
686
|
+
// Update finish reason
|
|
687
|
+
if (processed.finishReason) {
|
|
688
|
+
finalFinishReason = processed.finishReason;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
catch (parseError) {
|
|
692
|
+
logger.warn(`[OllamaProvider] Failed to parse stream chunk: ${dataLine}`, { error: parseError });
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
finally {
|
|
699
|
+
reader.releaseLock();
|
|
700
|
+
}
|
|
701
|
+
return {
|
|
702
|
+
content: aggregatedContent || undefined,
|
|
703
|
+
toolCalls: aggregatedToolCalls.length > 0 ? aggregatedToolCalls : undefined,
|
|
704
|
+
finishReason: finalFinishReason,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
427
707
|
/**
|
|
428
708
|
* Process individual stream data chunk from Ollama
|
|
429
709
|
*/
|
|
@@ -431,20 +711,33 @@ export class OllamaProvider extends BaseProvider {
|
|
|
431
711
|
const dataRecord = data;
|
|
432
712
|
const choices = dataRecord.choices;
|
|
433
713
|
const delta = choices?.[0]?.delta;
|
|
714
|
+
const finishReason = choices?.[0]?.finish_reason;
|
|
434
715
|
let content = "";
|
|
435
716
|
if (delta?.content && typeof delta.content === "string") {
|
|
436
717
|
content += delta.content;
|
|
437
718
|
}
|
|
719
|
+
// Return tool calls for execution instead of formatting as text
|
|
438
720
|
if (delta?.tool_calls) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
721
|
+
const toolCalls = this.parseToolCalls(delta.tool_calls);
|
|
722
|
+
return {
|
|
723
|
+
toolCalls,
|
|
724
|
+
finishReason,
|
|
725
|
+
shouldReturn: !!finishReason,
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
// Also check for tool calls in the message field (some responses include it there)
|
|
729
|
+
if (choices?.[0]?.message?.tool_calls) {
|
|
730
|
+
const toolCalls = this.parseToolCalls(choices[0].message.tool_calls);
|
|
731
|
+
return {
|
|
732
|
+
toolCalls,
|
|
733
|
+
finishReason,
|
|
734
|
+
shouldReturn: !!finishReason,
|
|
735
|
+
};
|
|
445
736
|
}
|
|
446
|
-
const shouldReturn = !!
|
|
447
|
-
return content
|
|
737
|
+
const shouldReturn = !!finishReason;
|
|
738
|
+
return content
|
|
739
|
+
? { content, finishReason, shouldReturn }
|
|
740
|
+
: { finishReason, shouldReturn };
|
|
448
741
|
}
|
|
449
742
|
/**
|
|
450
743
|
* Create stream generator for Ollama chat API with tool call support
|
|
@@ -522,6 +815,198 @@ export class OllamaProvider extends BaseProvider {
|
|
|
522
815
|
});
|
|
523
816
|
return descriptions.join("");
|
|
524
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Convert AI SDK tools to ToolDefinition format
|
|
820
|
+
*/
|
|
821
|
+
convertAISDKToolsToToolDefinitions(aiTools) {
|
|
822
|
+
const result = {};
|
|
823
|
+
for (const [name, tool] of Object.entries(aiTools)) {
|
|
824
|
+
if ("description" in tool && tool.description) {
|
|
825
|
+
result[name] = {
|
|
826
|
+
description: tool.description,
|
|
827
|
+
parameters: "parameters" in tool ? tool.parameters : undefined,
|
|
828
|
+
execute: async (params) => {
|
|
829
|
+
if ("execute" in tool && tool.execute) {
|
|
830
|
+
const result = await tool.execute(params, {
|
|
831
|
+
toolCallId: `tool_${Date.now()}`,
|
|
832
|
+
messages: [],
|
|
833
|
+
});
|
|
834
|
+
return {
|
|
835
|
+
success: true,
|
|
836
|
+
data: result,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
throw new Error(`Tool ${name} has no execute method`);
|
|
840
|
+
},
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return result;
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Execute a single tool and return the result
|
|
848
|
+
*/
|
|
849
|
+
async executeSingleTool(toolName, args, _toolCallId) {
|
|
850
|
+
logger.debug(`[OllamaProvider] Executing single tool: ${toolName}`, {
|
|
851
|
+
args,
|
|
852
|
+
});
|
|
853
|
+
try {
|
|
854
|
+
// Use BaseProvider's tool execution mechanism
|
|
855
|
+
const aiTools = await this.getAllTools();
|
|
856
|
+
const tools = this.convertAISDKToolsToToolDefinitions(aiTools);
|
|
857
|
+
if (!tools[toolName]) {
|
|
858
|
+
throw new Error(`Tool not found: ${toolName}`);
|
|
859
|
+
}
|
|
860
|
+
const tool = tools[toolName];
|
|
861
|
+
if (!tool || !tool.execute) {
|
|
862
|
+
throw new Error(`Tool ${toolName} does not have execute method`);
|
|
863
|
+
}
|
|
864
|
+
const toolInput = args || {};
|
|
865
|
+
// Convert Record<string, unknown> to ToolArgs by filtering out non-JsonValue types
|
|
866
|
+
const toolArgs = {};
|
|
867
|
+
for (const [key, value] of Object.entries(toolInput)) {
|
|
868
|
+
// Only include values that are JsonValue compatible
|
|
869
|
+
if (value === null ||
|
|
870
|
+
typeof value === "string" ||
|
|
871
|
+
typeof value === "number" ||
|
|
872
|
+
typeof value === "boolean" ||
|
|
873
|
+
(typeof value === "object" && value !== null)) {
|
|
874
|
+
toolArgs[key] = value;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const result = await tool.execute(toolArgs);
|
|
878
|
+
logger.debug(`[OllamaProvider] Tool execution result:`, {
|
|
879
|
+
toolName,
|
|
880
|
+
result,
|
|
881
|
+
});
|
|
882
|
+
// Handle ToolResult type
|
|
883
|
+
if (result && typeof result === "object" && "success" in result) {
|
|
884
|
+
if (result.success && result.data !== undefined) {
|
|
885
|
+
if (typeof result.data === "string") {
|
|
886
|
+
return result.data;
|
|
887
|
+
}
|
|
888
|
+
else if (typeof result.data === "object") {
|
|
889
|
+
return JSON.stringify(result.data, null, 2);
|
|
890
|
+
}
|
|
891
|
+
else {
|
|
892
|
+
return String(result.data);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
else if (result.error) {
|
|
896
|
+
throw new Error(result.error.message || "Tool execution failed");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
// Fallback for non-ToolResult return types
|
|
900
|
+
if (typeof result === "string") {
|
|
901
|
+
return result;
|
|
902
|
+
}
|
|
903
|
+
else if (typeof result === "object") {
|
|
904
|
+
return JSON.stringify(result, null, 2);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
return String(result);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
catch (error) {
|
|
911
|
+
logger.error(`[OllamaProvider] Tool execution error:`, {
|
|
912
|
+
toolName,
|
|
913
|
+
error,
|
|
914
|
+
});
|
|
915
|
+
throw error;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Execute tools and format results for Ollama API
|
|
920
|
+
* Similar to Bedrock's executeStreamTools but for Ollama format
|
|
921
|
+
*/
|
|
922
|
+
async executeOllamaTools(toolCalls, options) {
|
|
923
|
+
const toolResults = [];
|
|
924
|
+
const toolCallsForStorage = [];
|
|
925
|
+
const toolResultsForStorage = [];
|
|
926
|
+
logger.debug(`[OllamaProvider] Executing ${toolCalls.length} tool calls`);
|
|
927
|
+
for (const call of toolCalls) {
|
|
928
|
+
logger.debug(`[OllamaProvider] Executing tool: ${call.function.name}`);
|
|
929
|
+
// Parse arguments
|
|
930
|
+
let args = {};
|
|
931
|
+
try {
|
|
932
|
+
args = JSON.parse(call.function.arguments);
|
|
933
|
+
}
|
|
934
|
+
catch (error) {
|
|
935
|
+
logger.error(`[OllamaProvider] Failed to parse tool arguments: ${call.function.arguments}`, { error });
|
|
936
|
+
args = {};
|
|
937
|
+
}
|
|
938
|
+
// Track tool call for storage
|
|
939
|
+
toolCallsForStorage.push({
|
|
940
|
+
type: "tool-call",
|
|
941
|
+
toolCallId: call.id,
|
|
942
|
+
toolName: call.function.name,
|
|
943
|
+
args,
|
|
944
|
+
});
|
|
945
|
+
try {
|
|
946
|
+
// Execute tool using existing tool framework
|
|
947
|
+
const result = await this.executeSingleTool(call.function.name, args, call.id);
|
|
948
|
+
logger.debug(`[OllamaProvider] Tool execution successful: ${call.function.name}`);
|
|
949
|
+
// Track result for storage
|
|
950
|
+
toolResultsForStorage.push({
|
|
951
|
+
type: "tool-result",
|
|
952
|
+
toolCallId: call.id,
|
|
953
|
+
toolName: call.function.name,
|
|
954
|
+
result,
|
|
955
|
+
});
|
|
956
|
+
// Format for Ollama API
|
|
957
|
+
toolResults.push({
|
|
958
|
+
tool_call_id: call.id,
|
|
959
|
+
content: JSON.stringify(result),
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
catch (error) {
|
|
963
|
+
logger.error(`[OllamaProvider] Tool execution failed: ${call.function.name}`, { error });
|
|
964
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
965
|
+
// Track failed result
|
|
966
|
+
toolResultsForStorage.push({
|
|
967
|
+
type: "tool-result",
|
|
968
|
+
toolCallId: call.id,
|
|
969
|
+
toolName: call.function.name,
|
|
970
|
+
result: { error: errorMessage },
|
|
971
|
+
});
|
|
972
|
+
// Format error for Ollama API
|
|
973
|
+
toolResults.push({
|
|
974
|
+
tool_call_id: call.id,
|
|
975
|
+
content: JSON.stringify({ error: errorMessage }),
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
// Store tool executions (similar to Bedrock)
|
|
980
|
+
this.handleToolExecutionStorage(toolCallsForStorage, toolResultsForStorage, options, new Date()).catch((error) => {
|
|
981
|
+
logger.warn("[OllamaProvider] Failed to store tool executions", {
|
|
982
|
+
provider: this.providerName,
|
|
983
|
+
error: error instanceof Error ? error.message : String(error),
|
|
984
|
+
});
|
|
985
|
+
});
|
|
986
|
+
return toolResults;
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Convert ReadableStream to AsyncIterable for compatibility with StreamResult interface
|
|
990
|
+
*/
|
|
991
|
+
convertToAsyncIterable(stream) {
|
|
992
|
+
return {
|
|
993
|
+
async *[Symbol.asyncIterator]() {
|
|
994
|
+
const reader = stream.getReader();
|
|
995
|
+
try {
|
|
996
|
+
while (true) {
|
|
997
|
+
const { done, value } = await reader.read();
|
|
998
|
+
if (done) {
|
|
999
|
+
break;
|
|
1000
|
+
}
|
|
1001
|
+
yield value;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
finally {
|
|
1005
|
+
reader.releaseLock();
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
525
1010
|
/**
|
|
526
1011
|
* Create stream generator for Ollama generate API (non-tool mode)
|
|
527
1012
|
*/
|
|
@@ -668,3 +1153,4 @@ export class OllamaProvider extends BaseProvider {
|
|
|
668
1153
|
}
|
|
669
1154
|
}
|
|
670
1155
|
export default OllamaProvider;
|
|
1156
|
+
//# sourceMappingURL=ollama.js.map
|