@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
399 lines (398 loc) • 14.3 kB
JavaScript
/**
* workflow/config.ts
* Configuration schemas, validation, and defaults
*
* Uses Zod for runtime validation and type safety
*/
import { z } from "zod";
import { AIProviderName } from "../constants/enums.js";
// ============================================================================
// CONSTANTS
// ============================================================================
// Score scale constants for testing phase
export const MIN_SCORE = 0;
export const MAX_SCORE = 100;
// Reasoning length constraint for testing phase
export const MAX_REASONING_LENGTH = 200;
// Placeholder values for error cases
export const PLACEHOLDER_PROVIDER = "none";
export const PLACEHOLDER_MODEL = "none";
// Fixed creation timestamp for predefined workflows
export const WORKFLOW_CREATION_DATE = "2025-11-29T00:00:00.000Z";
// ============================================================================
// ZOD VALIDATION SCHEMAS
// ============================================================================
/**
* JSON-safe metadata validation
*/
const JsonValueSchema = z.lazy(() => z.union([
z.string(),
z.number(),
z.boolean(),
z.null(),
z.array(JsonValueSchema),
z.record(z.string(), JsonValueSchema),
]));
/**
* Provider name validation - accepts any AIProviderName enum value or string
*/
const ProviderNameSchema = z
.union([z.nativeEnum(AIProviderName), z.string().min(1)])
.transform((val) => val);
/**
* Model configuration schema
*/
export const ModelConfigSchema = z.object({
provider: ProviderNameSchema,
model: z.string().min(1, "Model name is required"),
weight: z.number().min(0).max(1).optional(),
temperature: z.number().min(0).max(2).optional(),
maxTokens: z.number().int().positive().optional(),
systemPrompt: z.string().optional(),
timeout: z.number().int().positive().optional(),
topP: z.number().min(0).max(1).optional(),
topK: z.number().int().positive().optional(),
presencePenalty: z.number().min(-2).max(2).optional(),
frequencyPenalty: z.number().min(-2).max(2).optional(),
label: z.string().optional(),
metadata: z.record(z.string(), JsonValueSchema).optional(),
});
/**
* Judge configuration schema
* NOTE: Testing phase enforces 0-100 score scale
*/
export const JudgeConfigSchema = z.object({
provider: ProviderNameSchema,
model: z.string().min(1, "Judge model is required"),
criteria: z.array(z.string()).min(1, "At least one criterion required"),
outputFormat: z.enum(["scores", "ranking", "best", "detailed"]),
customPrompt: z.string().optional(),
systemPrompt: z.string().optional(),
temperature: z.number().min(0).max(2).optional(),
maxTokens: z.number().int().positive().optional(),
timeout: z.number().int().positive().optional(),
blindEvaluation: z.boolean().optional(),
includeReasoning: z.boolean(),
scoreScale: z.object({
min: z.literal(0),
max: z.literal(100),
}),
label: z.string().optional(),
metadata: z.record(z.string(), JsonValueSchema).optional(),
});
/**
* Conditioning configuration schema
*/
export const ConditioningConfigSchema = z.object({
useConfidence: z.boolean(),
confidenceThresholds: z
.object({
high: z.number().min(0).max(1),
medium: z.number().min(0).max(1),
low: z.number().min(0).max(1),
})
.optional(),
toneAdjustment: z.enum(["soften", "strengthen", "neutral"]).optional(),
includeMetadata: z.boolean().optional(),
metadataFields: z.array(z.string()).optional(),
addConfidenceStatement: z.boolean().optional(),
addModelAttribution: z.boolean().optional(),
addExecutionTime: z.boolean().optional(),
metadata: z.record(z.string(), JsonValueSchema).optional(),
});
/**
* Model group schema for layer-based execution
*/
export const ModelGroupSchema = z.object({
id: z.string().min(1, "Group ID is required"),
name: z.string().optional(),
description: z.string().optional(),
models: z
.array(ModelConfigSchema)
.min(1, "Group must have at least one model"),
executionStrategy: z.enum(["parallel", "sequential"]),
continueOnFailure: z.boolean().optional().default(true),
minSuccessful: z.number().int().positive().optional().default(1),
parallelism: z.number().int().positive().optional(),
timeout: z.number().int().positive().optional(),
metadata: z.record(z.string(), JsonValueSchema).optional(),
});
/**
* Execution configuration schema
*/
export const ExecutionConfigSchema = z.object({
timeout: z.number().int().positive().optional(),
modelTimeout: z.number().int().positive().optional(),
judgeTimeout: z.number().int().positive().optional(),
retries: z.number().int().min(0).max(5).optional(),
retryDelay: z.number().int().positive().optional(),
retryableErrors: z.array(z.string()).optional(),
parallelism: z.number().int().positive().optional(),
earlyTermination: z.boolean().optional(),
minResponses: z.number().int().positive().optional(),
maxCost: z.number().positive().optional(),
costThreshold: z.number().positive().optional(),
enableMetrics: z.boolean().optional(),
enableTracing: z.boolean().optional(),
metadata: z.record(z.string(), JsonValueSchema).optional(),
});
/**
* Complete workflow configuration schema
*/
const WorkflowConfigSchemaBase = z.object({
id: z.string().min(1, "Workflow ID is required"),
name: z.string().min(1, "Workflow name is required"),
description: z.string().optional(),
version: z.string().optional(),
type: z.enum(["ensemble", "chain", "adaptive", "custom"]),
models: z.array(ModelConfigSchema).min(1, "At least one model required"),
modelGroups: z.array(ModelGroupSchema).optional(),
defaultSystemPrompt: z.string().optional(),
defaultJudgePrompt: z.string().optional(),
judge: JudgeConfigSchema.optional(),
judges: z.array(JudgeConfigSchema).optional(),
conditioning: ConditioningConfigSchema.optional(),
execution: ExecutionConfigSchema.optional(),
tags: z.array(z.string()).optional(),
metadata: z.record(z.string(), JsonValueSchema).optional(),
createdAt: z.string().optional(),
updatedAt: z.string().optional(),
});
export const WorkflowConfigSchema = WorkflowConfigSchemaBase.refine((data) => {
// Cannot have both judge and judges
if (data.judge && data.judges) {
return false;
}
return true;
}, {
message: 'Cannot specify both "judge" and "judges" - use one or the other',
}).refine((data) => {
// Ensemble and adaptive need at least 2 models
// Check flat models array if modelGroups not provided
if (data.type === "ensemble" || data.type === "adaptive") {
if (data.modelGroups && data.modelGroups.length > 0) {
// Count total models across all groups
const totalModels = data.modelGroups.reduce((sum, group) => sum + group.models.length, 0);
return totalModels >= 2;
}
else {
// Check flat models array
return data.models.length >= 2;
}
}
return true;
}, {
message: "Ensemble and adaptive workflows require at least 2 models",
});
// ============================================================================
// DEFAULT CONFIGURATIONS
// ============================================================================
/**
* Default conditioning configuration
* NOTE: Testing phase - stub only, no actual conditioning applied
*/
export const DEFAULT_CONDITIONING_CONFIG = {
useConfidence: true,
confidenceThresholds: {
high: 0.8,
medium: 0.5,
low: 0.3,
},
toneAdjustment: "neutral",
includeMetadata: false,
addConfidenceStatement: false,
addModelAttribution: false,
addExecutionTime: false,
};
/**
* Default execution configuration
*/
export const DEFAULT_EXECUTION_CONFIG = {
timeout: 30000, // 30 seconds total
modelTimeout: 15000, // 15 seconds per model
judgeTimeout: 10000, // 10 seconds for judge
retries: 1,
retryDelay: 1000,
retryableErrors: ["ETIMEDOUT", "ECONNRESET", "ENOTFOUND"],
parallelism: 10,
earlyTermination: false,
minResponses: 1,
enableMetrics: true,
enableTracing: false,
};
/**
* Default score scale (0-100 for testing phase)
*/
export const DEFAULT_SCORE_SCALE = {
min: 0,
max: 100,
};
// ============================================================================
// HELPER FUNCTIONS FOR MODEL GROUPS
// ============================================================================
/**
* Check if workflow uses layer-based execution (modelGroups)
* @param config - Workflow configuration
* @returns True if modelGroups is defined and has groups
*/
export function usesModelGroups(config) {
return !!(config.modelGroups && config.modelGroups.length > 0);
}
/**
* Get all models from workflow (either from flat array or groups)
* @param config - Workflow configuration
* @returns Array of all model configs
*/
export function getAllModels(config) {
if (usesModelGroups(config)) {
return config.modelGroups?.flatMap((group) => group.models) ?? [];
}
return config.models;
}
/**
* Get model groups (converts flat models to single group if needed)
* @param config - Workflow configuration
* @returns Array of model groups
*/
export function getModelGroups(config) {
if (usesModelGroups(config)) {
return config.modelGroups ?? [];
}
// Convert flat models array to single parallel group for backward compatibility
return [
{
id: "default-group",
name: "All Models",
models: config.models,
executionStrategy: "parallel",
continueOnFailure: true,
minSuccessful: config.execution?.minResponses || 1,
},
];
}
/**
* Default judge configuration values
*/
export const DEFAULT_JUDGE_CONFIG = {
temperature: 0.1, // Low temperature for consistent judging
outputFormat: "detailed",
blindEvaluation: false,
includeReasoning: true,
scoreScale: DEFAULT_SCORE_SCALE,
};
// ============================================================================
// CONFIGURATION HELPERS
// ============================================================================
/**
* Merge configuration with defaults
* @param config - Workflow configuration to merge
* @returns Complete workflow configuration with defaults applied
*/
export function mergeWithDefaults(config) {
return {
...config,
conditioning: config.conditioning
? { ...DEFAULT_CONDITIONING_CONFIG, ...config.conditioning }
: DEFAULT_CONDITIONING_CONFIG,
execution: config.execution
? { ...DEFAULT_EXECUTION_CONFIG, ...config.execution }
: DEFAULT_EXECUTION_CONFIG,
createdAt: config.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
}
/**
* Validate workflow configuration
* @param config - Partial workflow configuration to validate
* @returns Validation result with parsed data or error details
*/
export function validateWorkflowConfig(config) {
const result = WorkflowConfigSchema.safeParse(config);
if (result.success) {
return { success: true, data: result.data };
}
return { success: false, error: result.error };
}
/**
* Create workflow configuration from partial
* @param partial - Partial configuration with required fields (id, name, type, models)
* @returns Complete workflow configuration with defaults applied
*/
export function createWorkflowConfig(partial) {
const base = {
id: partial.id,
name: partial.name,
type: partial.type,
models: partial.models,
description: partial.description,
version: partial.version,
judge: partial.judge,
judges: partial.judges,
conditioning: partial.conditioning,
execution: partial.execution,
tags: partial.tags,
metadata: partial.metadata,
};
return mergeWithDefaults(base);
}
/**
* Validate model configuration
* @param config - Partial model configuration to validate
* @returns Validation result with parsed data or error details
*/
export function validateModelConfig(config) {
const result = ModelConfigSchema.safeParse(config);
if (result.success) {
return { success: true, data: result.data };
}
return { success: false, error: result.error };
}
/**
* Validate judge configuration
* @param config - Partial judge configuration to validate
* @returns Validation result with parsed data or error details
*/
export function validateJudgeConfig(config) {
const result = JudgeConfigSchema.safeParse(config);
if (result.success) {
return { success: true, data: result.data };
}
return { success: false, error: result.error };
}
/**
* Check if workflow has judge configuration
* @param config - Workflow configuration to check
* @returns True if workflow has at least one judge configured
*/
export function hasJudge(config) {
return !!(config.judge || (config.judges && config.judges.length > 0));
}
/**
* Get all judges from workflow configuration
* @param config - Workflow configuration
* @returns Array of all judge configurations (empty if none)
*/
export function getAllJudges(config) {
if (config.judges && config.judges.length > 0) {
return config.judges;
}
if (config.judge) {
return [config.judge];
}
return [];
}
/**
* Calculate estimated workflow cost (placeholder)
* TODO: Implement actual provider-specific pricing
* @param config - Workflow configuration
* @param estimatedTokens - Estimated number of tokens for the request
* @returns Estimated cost in USD
*/
export function estimateWorkflowCost(config, estimatedTokens) {
const modelCount = config.models.length;
const judgeCount = getAllJudges(config).length;
// Placeholder cost calculation ($0.00001 per token)
const modelsCost = modelCount * estimatedTokens * 0.00001;
const judgesCost = judgeCount * estimatedTokens * 0.5 * 0.00001; // Judges use ~50% tokens
return modelsCost + judgesCost;
}