UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with external MCP server integration, multi-provider support, and professional CLI. Connect to 65+ MCP servers for filesystem, GitHub, database operations, and more. Build, test, and deploy AI applications with 9 major pr

410 lines (409 loc) 18.1 kB
import { createVertex } from '@ai-sdk/google-vertex'; // Cache for anthropic module to avoid repeated imports let _createVertexAnthropic = null; let _anthropicImportAttempted = false; // Function to dynamically import anthropic support async function getCreateVertexAnthropic() { if (_anthropicImportAttempted) { return _createVertexAnthropic; } _anthropicImportAttempted = true; try { // Try to import the anthropic module - available in @ai-sdk/google-vertex ^2.2.0+ const anthropicModule = await import('@ai-sdk/google-vertex/anthropic'); _createVertexAnthropic = anthropicModule.createVertexAnthropic; logger.debug('[GoogleVertexAI] Anthropic module successfully loaded'); return _createVertexAnthropic; } catch (error) { // Anthropic module not available logger.warn('[GoogleVertexAI] Anthropic module not available. Install @ai-sdk/google-vertex ^2.2.0 for Anthropic model support.'); return null; } } import { streamText, generateText, Output } from 'ai'; import { logger } from '../utils/logger.js'; // Default system context const DEFAULT_SYSTEM_CONTEXT = { systemPrompt: 'You are a helpful AI assistant.' }; // Configuration helpers const getGCPVertexBreezeProjectId = () => { const projectId = process.env.GOOGLE_VERTEX_PROJECT; if (!projectId) { throw new Error('GOOGLE_VERTEX_PROJECT environment variable is not set'); } return projectId; }; const getGCPVertexBreezeLocation = () => { return process.env.GOOGLE_VERTEX_LOCATION || 'us-east5'; }; const getGoogleApplicationCredentials = () => { return process.env.GOOGLE_APPLICATION_CREDENTIALS; }; const getGoogleServiceAccountKey = () => { return process.env.GOOGLE_SERVICE_ACCOUNT_KEY; }; const getGoogleClientEmail = () => { return process.env.GOOGLE_AUTH_CLIENT_EMAIL; }; const getGooglePrivateKey = () => { return process.env.GOOGLE_AUTH_PRIVATE_KEY; }; const getVertexModelId = () => { return process.env.VERTEX_MODEL_ID || 'claude-sonnet-4@20250514'; }; const hasPrincipalAccountAuth = () => { return !!getGoogleApplicationCredentials(); }; const hasServiceAccountKeyAuth = () => { return !!getGoogleServiceAccountKey(); }; const hasServiceAccountEnvAuth = () => { return !!(getGoogleClientEmail() && getGooglePrivateKey()); }; const hasValidAuth = () => { return hasPrincipalAccountAuth() || hasServiceAccountKeyAuth() || hasServiceAccountEnvAuth(); }; // Setup environment for Google authentication const setupGoogleAuth = async () => { const functionTag = 'setupGoogleAuth'; // Method 2: Service Account Key (JSON string) - Create temporary file if (hasServiceAccountKeyAuth() && !hasPrincipalAccountAuth()) { const serviceAccountKey = getGoogleServiceAccountKey(); logger.debug(`[${functionTag}] Service account key auth (JSON string)`, { hasServiceAccountKey: !!serviceAccountKey, authMethod: 'service_account_key' }); try { // Parse to validate JSON JSON.parse(serviceAccountKey); // Write to temporary file and set environment variable using dynamic imports const { writeFileSync } = await import('fs'); const { join } = await import('path'); const { tmpdir } = await import('os'); const tempFile = join(tmpdir(), `gcp-credentials-${Date.now()}.json`); writeFileSync(tempFile, serviceAccountKey); process.env.GOOGLE_APPLICATION_CREDENTIALS = tempFile; logger.debug(`[${functionTag}] Created temporary credentials file`, { tempFile: '[CREATED]', authMethod: 'service_account_key_temp_file' }); } catch (error) { logger.error(`[${functionTag}] Failed to parse service account key`, { error: error instanceof Error ? error.message : String(error) }); throw new Error('Invalid GOOGLE_SERVICE_ACCOUNT_KEY format. Must be valid JSON.'); } } // Method 3: Service Account Environment Variables - Set as individual env vars if (hasServiceAccountEnvAuth() && !hasPrincipalAccountAuth() && !hasServiceAccountKeyAuth()) { const clientEmail = getGoogleClientEmail(); const privateKey = getGooglePrivateKey(); logger.debug(`[${functionTag}] Service account env auth (separate variables)`, { hasClientEmail: !!clientEmail, hasPrivateKey: !!privateKey, authMethod: 'service_account_env' }); // Create service account object and write to temporary file const serviceAccount = { type: 'service_account', project_id: getGCPVertexBreezeProjectId(), client_email: clientEmail, private_key: privateKey.replace(/\\n/g, '\n'), auth_uri: 'https://accounts.google.com/o/oauth2/auth', token_uri: 'https://oauth2.googleapis.com/token' }; try { // Use dynamic imports for ESM compatibility const { writeFileSync } = await import('fs'); const { join } = await import('path'); const { tmpdir } = await import('os'); const tempFile = join(tmpdir(), `gcp-credentials-env-${Date.now()}.json`); writeFileSync(tempFile, JSON.stringify(serviceAccount, null, 2)); process.env.GOOGLE_APPLICATION_CREDENTIALS = tempFile; logger.debug(`[${functionTag}] Created temporary credentials file from env vars`, { tempFile: '[CREATED]', authMethod: 'service_account_env_temp_file' }); } catch (error) { logger.error(`[${functionTag}] Failed to create service account file from env vars`, { error: error instanceof Error ? error.message : String(error) }); throw new Error('Failed to create temporary service account file from environment variables.'); } } }; // Vertex AI setup with multiple authentication support const createVertexSettings = async () => { const functionTag = 'createVertexSettings'; // Setup authentication first await setupGoogleAuth(); const baseSettings = { project: getGCPVertexBreezeProjectId(), location: getGCPVertexBreezeLocation() }; // Method 1: Principal Account Authentication (file path) - Recommended for production if (hasPrincipalAccountAuth()) { const credentialsPath = getGoogleApplicationCredentials(); logger.debug(`[${functionTag}] Principal account auth (file path)`, { credentialsPath: credentialsPath ? '[PROVIDED]' : '[NOT_PROVIDED]', authMethod: 'principal_account_file' }); return baseSettings; } // Method 2 & 3: Other methods now set GOOGLE_APPLICATION_CREDENTIALS in setupGoogleAuth() if (hasServiceAccountKeyAuth() || hasServiceAccountEnvAuth()) { logger.debug(`[${functionTag}] Alternative auth method configured`, { authMethod: hasServiceAccountKeyAuth() ? 'service_account_key' : 'service_account_env', credentialsSet: !!process.env.GOOGLE_APPLICATION_CREDENTIALS }); return baseSettings; } // No valid authentication found logger.error(`[${functionTag}] No valid authentication method found`, { authMethod: 'none', hasPrincipalAccount: hasPrincipalAccountAuth(), hasServiceAccountKey: hasServiceAccountKeyAuth(), hasServiceAccountEnv: hasServiceAccountEnvAuth(), availableMethods: [ 'GOOGLE_APPLICATION_CREDENTIALS (file path)', 'GOOGLE_SERVICE_ACCOUNT_KEY (JSON string)', 'GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY (env vars)' ] }); throw new Error('No valid Google Vertex AI authentication found. Please provide one of:\n' + '1. GOOGLE_APPLICATION_CREDENTIALS (path to service account file)\n' + '2. GOOGLE_SERVICE_ACCOUNT_KEY (JSON string of service account)\n' + '3. GOOGLE_AUTH_CLIENT_EMAIL + GOOGLE_AUTH_PRIVATE_KEY (environment variables)'); }; // Helper function to determine if a model is an Anthropic model const isAnthropicModel = (modelName) => { // Anthropic models in Vertex AI contain "claude" anywhere in the model name return modelName.toLowerCase().includes('claude'); }; // Lazy initialization cache let _vertex = null; async function getVertexInstance() { if (!_vertex) { const settings = await createVertexSettings(); _vertex = createVertex(settings); } return _vertex; } // Google Vertex AI class with enhanced error handling and Anthropic model support export class GoogleVertexAI { modelName; /** * Initializes a new instance of GoogleVertexAI * @param modelName - Optional model name to override the default from config */ constructor(modelName) { const functionTag = 'GoogleVertexAI.constructor'; this.modelName = modelName || getVertexModelId(); try { logger.debug(`[${functionTag}] Initialization started`, { modelName: this.modelName, isAnthropic: isAnthropicModel(this.modelName) }); const hasPrincipal = hasPrincipalAccountAuth(); logger.debug(`[${functionTag}] Authentication validation`, { hasPrincipalAccountAuth: hasPrincipal, projectId: getGCPVertexBreezeProjectId() || 'MISSING', location: getGCPVertexBreezeLocation() || 'MISSING' }); if (hasPrincipal) { logger.debug(`[${functionTag}] Auth method selected`, { authMethod: 'principal_account', hasGoogleApplicationCredentials: !!getGoogleApplicationCredentials() }); } else { logger.warn(`[${functionTag}] Auth method missing`, { authMethod: 'none', hasPrincipalAccountAuth: hasPrincipal }); } logger.debug(`[${functionTag}] Initialization completed`, { modelName: this.modelName, isAnthropic: isAnthropicModel(this.modelName), authMethod: hasPrincipalAccountAuth() ? 'principal_account' : 'none', success: true }); } catch (err) { logger.error(`[${functionTag}] Initialization failed`, { message: 'Error in initializing Google Vertex AI', modelName: this.modelName, isAnthropic: isAnthropicModel(this.modelName), error: err instanceof Error ? err.message : String(err), stack: err instanceof Error ? err.stack : undefined }); } } /** * Gets the appropriate model instance (Google or Anthropic) * @private */ async getModel() { if (isAnthropicModel(this.modelName)) { logger.debug('GoogleVertexAI.getModel - Anthropic model selected', { modelName: this.modelName }); const createVertexAnthropic = await getCreateVertexAnthropic(); if (!createVertexAnthropic) { throw new Error(`Anthropic model "${this.modelName}" requested but @ai-sdk/google-vertex/anthropic is not available. ` + 'Please install @ai-sdk/google-vertex ^2.2.0 or use a Google model instead.'); } const settings = await createVertexSettings(); const vertexAnthropic = createVertexAnthropic(settings); return vertexAnthropic(this.modelName); } const vertex = await getVertexInstance(); return vertex(this.modelName); } /** * Processes text using streaming approach with enhanced error handling callbacks * @param prompt - The input text prompt to analyze * @param analysisSchema - Optional Zod schema or Schema object for output validation * @returns Promise resolving to StreamTextResult or null if operation fails */ async streamText(optionsOrPrompt, analysisSchema) { const functionTag = 'GoogleVertexAI.streamText'; const provider = 'vertex'; let chunkCount = 0; try { // Parse parameters - support both string and options object const options = typeof optionsOrPrompt === 'string' ? { prompt: optionsOrPrompt } : optionsOrPrompt; const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = DEFAULT_SYSTEM_CONTEXT.systemPrompt, schema } = options; // Use schema from options or fallback parameter const finalSchema = schema || analysisSchema; logger.debug(`[${functionTag}] Stream request started`, { provider, modelName: this.modelName, isAnthropic: isAnthropicModel(this.modelName), promptLength: prompt.length, temperature, maxTokens, hasSchema: !!finalSchema }); const model = await this.getModel(); const streamOptions = { model: model, prompt: prompt, system: systemPrompt, temperature, maxTokens, onError: (event) => { const error = event.error; const errorMessage = error instanceof Error ? error.message : String(error); const errorStack = error instanceof Error ? error.stack : undefined; logger.error(`[${functionTag}] Stream text error`, { provider, modelName: this.modelName, error: errorMessage, stack: errorStack, promptLength: prompt.length, chunkCount }); }, onFinish: (event) => { logger.debug(`[${functionTag}] Stream text finished`, { provider, modelName: this.modelName, finishReason: event.finishReason, usage: event.usage, totalChunks: chunkCount, promptLength: prompt.length, responseLength: event.text?.length || 0 }); }, onChunk: (event) => { chunkCount++; logger.debug(`[${functionTag}] Stream text chunk`, { provider, modelName: this.modelName, chunkNumber: chunkCount, chunkLength: event.chunk.text?.length || 0, chunkType: event.chunk.type }); } }; if (analysisSchema) { streamOptions.experimental_output = Output.object({ schema: analysisSchema }); } const result = streamText(streamOptions); return result; } catch (err) { logger.error(`[${functionTag}] Exception`, { provider, modelName: this.modelName, message: 'Error in streaming text', err: String(err), promptLength: prompt.length }); throw err; // Re-throw error to trigger fallback } } /** * Processes text using non-streaming approach with optional schema validation * @param prompt - The input text prompt to analyze * @param analysisSchema - Optional Zod schema or Schema object for output validation * @returns Promise resolving to GenerateTextResult or null if operation fails */ async generateText(optionsOrPrompt, analysisSchema) { const functionTag = 'GoogleVertexAI.generateText'; const provider = 'vertex'; try { // Parse parameters - support both string and options object const options = typeof optionsOrPrompt === 'string' ? { prompt: optionsOrPrompt } : optionsOrPrompt; const { prompt, temperature = 0.7, maxTokens = 500, systemPrompt = DEFAULT_SYSTEM_CONTEXT.systemPrompt, schema } = options; // Use schema from options or fallback parameter const finalSchema = schema || analysisSchema; logger.debug(`[${functionTag}] Generate request started`, { provider, modelName: this.modelName, isAnthropic: isAnthropicModel(this.modelName), promptLength: prompt.length, temperature, maxTokens }); const model = await this.getModel(); const generateOptions = { model: model, prompt: prompt, system: systemPrompt, temperature, maxTokens }; if (finalSchema) { generateOptions.experimental_output = Output.object({ schema: finalSchema }); } const result = await generateText(generateOptions); logger.debug(`[${functionTag}] Generate text completed`, { provider, modelName: this.modelName, usage: result.usage, finishReason: result.finishReason, responseLength: result.text?.length || 0 }); return result; } catch (err) { logger.error(`[${functionTag}] Exception`, { provider, modelName: this.modelName, message: 'Error in generating text', err: String(err) }); throw err; // Re-throw error to trigger fallback } } }