@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
JavaScript
/**
* 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;
}