UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

421 lines (420 loc) 15.5 kB
/** * workflow/utils/workflowValidation.ts * Validation utilities for workflow configurations and execution */ import { logger } from "../../utils/logger.js"; import { getAllJudges, hasJudge, MAX_SCORE, MIN_SCORE, validateWorkflowConfig, } from "../config.js"; const functionTag = "WorkflowValidation"; // ============================================================================ // VALIDATION FUNCTIONS // ============================================================================ /** * Comprehensive workflow validation * @param config - Workflow configuration to validate * @returns Validation result with errors and warnings */ export function validateWorkflow(config) { const errors = []; const warnings = []; // Schema validation const schemaResult = validateWorkflowConfig(config); if (!schemaResult.success && schemaResult.error) { schemaResult.error.issues.forEach((err) => { errors.push({ field: err.path.join("."), message: err.message, code: "SCHEMA_VALIDATION_ERROR", severity: "error", }); }); } // Validate models const modelValidation = validateModels(config.models); errors.push(...modelValidation.errors); warnings.push(...modelValidation.warnings); // Validate judge configuration (if present) if (hasJudge(config)) { const judgeValidation = validateJudges(getAllJudges(config)); errors.push(...judgeValidation.errors); warnings.push(...judgeValidation.warnings); } // Validate workflow type-specific requirements const typeValidation = validateWorkflowType(config); errors.push(...typeValidation.errors); warnings.push(...typeValidation.warnings); // Validate execution configuration const executionValidation = validateExecutionConfig(config); errors.push(...executionValidation.errors); warnings.push(...executionValidation.warnings); // Check for conflicts const conflictValidation = validateNoConflicts(config); errors.push(...conflictValidation.errors); warnings.push(...conflictValidation.warnings); return { valid: errors.length === 0, errors, warnings, }; } /** * Validate model configurations * @param models - Array of model configurations to validate * @returns Validation issues including errors and warnings */ function validateModels(models) { const errors = []; const warnings = []; if (models.length === 0) { errors.push({ field: "models", message: "At least one model is required", code: "NO_MODELS", severity: "critical", }); return { errors, warnings }; } // Validate each model models.forEach((model, index) => { if (!model.provider || model.provider.trim() === "") { errors.push({ field: `models[${index}].provider`, message: "Model provider is required", code: "MISSING_PROVIDER", severity: "error", }); } if (!model.model || model.model.trim() === "") { errors.push({ field: `models[${index}].model`, message: "Model name is required", code: "MISSING_MODEL_NAME", severity: "error", }); } // Validate temperature range if (model.temperature !== undefined) { if (model.temperature < 0 || model.temperature > 2) { warnings.push({ field: `models[${index}].temperature`, message: `Temperature ${model.temperature} is outside typical range (0-2)`, code: "TEMPERATURE_OUT_OF_RANGE", recommendation: "Use temperature between 0 and 1 for most use cases", }); } } // Validate timeout if (model.timeout !== undefined && model.timeout < 1000) { warnings.push({ field: `models[${index}].timeout`, message: `Timeout ${model.timeout}ms is very short`, code: "SHORT_TIMEOUT", recommendation: "Consider using at least 5000ms for model timeout", }); } // Validate weight if (model.weight !== undefined) { if (model.weight < 0 || model.weight > 1) { errors.push({ field: `models[${index}].weight`, message: `Weight must be between 0 and 1, got ${model.weight}`, code: "INVALID_WEIGHT", severity: "error", }); } } }); // Check for duplicate models const modelKeys = models.map((m) => `${m.provider}/${m.model}`); const duplicates = modelKeys.filter((key, index) => modelKeys.indexOf(key) !== index); if (duplicates.length > 0) { warnings.push({ field: "models", message: `Duplicate models detected: ${duplicates.join(", ")}`, code: "DUPLICATE_MODELS", recommendation: "Using the same model multiple times may not add value", }); } // Warn if too many models (cost concern) if (models.length > 5) { warnings.push({ field: "models", message: `Using ${models.length} models may result in high costs and latency`, code: "MANY_MODELS", recommendation: "Consider using 2-4 models for optimal cost/quality balance", }); } return { errors, warnings }; } /** * Validate judge configurations * @param judges - Array of judge configurations to validate * @returns Validation issues including errors and warnings */ function validateJudges(judges) { const errors = []; const warnings = []; judges.forEach((judge, index) => { const prefix = judges.length > 1 ? `judges[${index}]` : "judge"; // Validate required fields if (!judge.provider || judge.provider.trim() === "") { errors.push({ field: `${prefix}.provider`, message: "Judge provider is required", code: "MISSING_JUDGE_PROVIDER", severity: "error", }); } if (!judge.model || judge.model.trim() === "") { errors.push({ field: `${prefix}.model`, message: "Judge model is required", code: "MISSING_JUDGE_MODEL", severity: "error", }); } if (!judge.criteria || judge.criteria.length === 0) { errors.push({ field: `${prefix}.criteria`, message: "At least one evaluation criterion is required", code: "NO_CRITERIA", severity: "error", }); } // Validate score scale (must be MIN_SCORE-MAX_SCORE for testing phase) if (judge.scoreScale) { if (judge.scoreScale.min !== MIN_SCORE || judge.scoreScale.max !== MAX_SCORE) { errors.push({ field: "judge.scoreScale", message: `Score scale must be ${MIN_SCORE}-${MAX_SCORE} for testing phase`, code: "INVALID_SCORE_SCALE", severity: "error", }); } } // Check includeReasoning is true if (!judge.includeReasoning) { errors.push({ field: `${prefix}.includeReasoning`, message: "includeReasoning must be true for testing phase", code: "REASONING_REQUIRED", severity: "error", }); } // Warn if temperature is high if (judge.temperature !== undefined && judge.temperature > 0.3) { warnings.push({ field: `${prefix}.temperature`, message: `Judge temperature ${judge.temperature} is high`, code: "HIGH_JUDGE_TEMPERATURE", recommendation: "Use low temperature (0.1-0.2) for consistent judge evaluation", }); } }); // Warn if too many judges if (judges.length > 3) { warnings.push({ field: "judges", message: `Using ${judges.length} judges may result in high costs`, code: "MANY_JUDGES", recommendation: "Consider using 1-2 judges for cost efficiency", }); } return { errors, warnings }; } /** * Validate workflow type-specific requirements * @param config - Workflow configuration to validate * @returns Validation issues specific to workflow type */ function validateWorkflowType(config) { const errors = []; const warnings = []; switch (config.type) { case "ensemble": if (config.models.length < 2) { errors.push({ field: "models", message: "Ensemble workflows require at least 2 models", code: "INSUFFICIENT_MODELS", severity: "error", }); } if (!hasJudge(config)) { warnings.push({ field: "judge", message: "Ensemble workflows typically benefit from judge scoring", code: "NO_JUDGE", recommendation: "Consider adding a judge to evaluate responses", }); } break; case "chain": // Chain workflows can have 1+ models if (config.models.length === 1) { warnings.push({ field: "models", message: "Chain workflow has only one model", code: "SINGLE_MODEL_CHAIN", recommendation: "Chain workflows are most useful with multiple fallback models", }); } break; case "adaptive": if (config.models.length < 2) { errors.push({ field: "models", message: "Adaptive workflows require at least 2 models", code: "INSUFFICIENT_MODELS", severity: "error", }); } break; case "custom": // Custom workflows have flexible requirements break; } return { errors, warnings }; } /** * Validate execution configuration * @param config - Workflow configuration with execution settings * @returns Validation issues related to execution configuration */ function validateExecutionConfig(config) { const errors = []; const warnings = []; if (!config.execution) { return { errors, warnings }; } const exec = config.execution; // Validate timeout hierarchy if (exec.timeout && exec.modelTimeout && exec.timeout < exec.modelTimeout) { errors.push({ field: "execution.timeout", message: "Total timeout cannot be less than model timeout", code: "INVALID_TIMEOUT_HIERARCHY", severity: "error", }); } // Validate minResponses vs model count if (exec.minResponses && exec.minResponses > config.models.length) { errors.push({ field: "execution.minResponses", message: `minResponses (${exec.minResponses}) cannot exceed model count (${config.models.length})`, code: "INVALID_MIN_RESPONSES", severity: "error", }); } // Warn about aggressive parallelism if (exec.parallelism && exec.parallelism > 20) { warnings.push({ field: "execution.parallelism", message: `High parallelism (${exec.parallelism}) may strain resources`, code: "HIGH_PARALLELISM", recommendation: "Consider using parallelism of 10-15 for stability", }); } return { errors, warnings }; } /** * Check for configuration conflicts * @param config - Workflow configuration to check for conflicts * @returns Validation issues related to conflicting settings */ function validateNoConflicts(config) { const errors = []; const warnings = []; // Cannot have both judge and judges if (config.judge && config.judges && config.judges.length > 0) { errors.push({ field: "judge", message: 'Cannot specify both "judge" and "judges"', code: "CONFLICTING_JUDGE_CONFIG", severity: "error", }); } // Early termination requires minResponses if (config.execution?.earlyTermination && !config.execution?.minResponses) { warnings.push({ field: "execution.earlyTermination", message: "Early termination without minResponses may stop too early", code: "EARLY_TERMINATION_WITHOUT_MIN", recommendation: "Set minResponses when using early termination", }); } return { errors, warnings }; } /** * Log validation results * @param workflowId - ID of the workflow being validated * @param result - Validation result to log */ export function logValidationResults(workflowId, result) { if (!result.valid) { logger.error(`[${functionTag}] Workflow validation failed`, { workflowId, errorCount: result.errors.length, errors: result.errors.map((e) => ({ field: e.field, message: e.message, code: e.code, })), }); } if (result.warnings.length > 0) { logger.warn(`[${functionTag}] Workflow validation warnings`, { workflowId, warningCount: result.warnings.length, warnings: result.warnings.map((w) => ({ field: w.field, message: w.message, code: w.code, })), }); } if (result.valid && result.warnings.length === 0) { logger.debug(`[${functionTag}] Workflow validation passed`, { workflowId, }); } } /** * Validate workflow at registration time * @param config - Workflow configuration to validate for registration * @returns Validation result with registration-specific checks */ export function validateForRegistration(config) { const result = validateWorkflow(config); // Additional registration-specific checks if (!config.id || config.id.trim() === "") { result.errors.push({ field: "id", message: "Workflow ID is required for registration", code: "MISSING_ID", severity: "critical", }); result.valid = false; } if (!config.name || config.name.trim() === "") { result.errors.push({ field: "name", message: "Workflow name is required for registration", code: "MISSING_NAME", severity: "critical", }); result.valid = false; } return result; } /** * Validate workflow at execution time * @param config - Workflow configuration to validate for execution * @returns Validation result for execution-time checks */ export function validateForExecution(config) { const result = validateWorkflow(config); // Execution-time checks are less strict // We allow warnings but fail on errors return result; }