@plust/datasleuth
Version:
Build LLM-powered research pipelines and output structured data.
190 lines • 8.39 kB
JavaScript
import { BaseResearchError, isResearchError } from '../types/errors.js';
import { createStepLogger } from './logging.js';
import { executeWithRetry } from './retry.js';
/**
* Creates a new step with consistent structure and error handling
*
* @param name Name of the step
* @param executor Function that executes the step logic
* @param options Step options
* @param creationOptions Error handling and retry configuration
* @returns A research step with standardized error handling
*/
export function createStep(name, executor, options = {}, creationOptions = {}) {
const stepLogger = createStepLogger(name);
// Create the step with standardized execution
const step = {
name,
options,
retryable: creationOptions.retryable ?? false,
optional: creationOptions.optional ?? false,
async execute(state) {
const startTime = Date.now();
stepLogger.info(`Starting execution`);
// Function that handles the actual execution
const executeFunc = async () => {
try {
// Update state to indicate current step
const updatedState = {
...state,
metadata: {
...state.metadata,
currentStep: name,
},
};
// Execute the step logic
const result = await executor(updatedState, options);
// Log success
const duration = Date.now() - startTime;
stepLogger.info(`Execution completed successfully in ${duration}ms`);
return result;
}
catch (error) {
// Transform error to a ResearchError if needed
let researchError;
if (isResearchError(error)) {
researchError = error;
}
else if (error instanceof Error) {
researchError = new BaseResearchError({
message: error.message,
code: 'step_execution_error',
step: name,
details: { originalError: error, stack: error.stack },
});
}
else {
researchError = new BaseResearchError({
message: `Unknown error in step ${name}`,
code: 'unknown_error',
step: name,
details: { originalError: error },
});
}
// Log error
const duration = Date.now() - startTime;
stepLogger.error(`Execution failed in ${duration}ms: ${researchError.getFormattedMessage()}`);
throw researchError;
}
};
// If step is configured for retry, use the retry mechanism
if (creationOptions.retryable && step.retryable) {
return executeWithRetry(executeFunc, {
maxRetries: creationOptions.maxRetries ?? 3,
retryDelay: creationOptions.retryDelay ?? 1000,
backoffFactor: creationOptions.backoffFactor ?? 2,
onRetry: (attempt, error, delay) => {
stepLogger.warn(`Retry attempt ${attempt} after error: ${error instanceof Error ? error.message : 'Unknown error'}. ` +
`Retrying in ${delay}ms...`);
},
});
}
else {
return executeFunc();
}
},
};
return step;
}
/**
* Wrap an existing step with enhanced error handling
*
* @param step The original step to wrap
* @param creationOptions Error handling and retry configuration
* @returns A wrapped step with enhanced error handling
*/
export function wrapStepWithErrorHandling(step, creationOptions = {}) {
const stepLogger = createStepLogger(step.name);
// Create a new step that wraps the original
const wrappedStep = {
name: step.name,
options: step.options,
retryable: creationOptions.retryable ?? step.retryable ?? false,
optional: creationOptions.optional ?? step.optional ?? false,
async execute(state) {
const startTime = Date.now();
stepLogger.info(`Starting execution of wrapped step`);
// Function that executes the original step
const executeFunc = async () => {
try {
// Update state to indicate current step
const updatedState = {
...state,
metadata: {
...state.metadata,
currentStep: step.name,
},
};
// Execute the original step
const result = await step.execute(updatedState);
// Log success
const duration = Date.now() - startTime;
stepLogger.info(`Execution completed successfully in ${duration}ms`);
return result;
}
catch (error) {
// Transform error to a ResearchError if needed
let researchError;
if (isResearchError(error)) {
researchError = error;
}
else if (error instanceof Error) {
researchError = new BaseResearchError({
message: error.message,
code: 'step_execution_error',
step: step.name,
details: { originalError: error, stack: error.stack },
});
}
else {
researchError = new BaseResearchError({
message: `Unknown error in step ${step.name}`,
code: 'unknown_error',
step: step.name,
details: { originalError: error },
});
}
// Log error
const duration = Date.now() - startTime;
stepLogger.error(`Execution failed in ${duration}ms: ${researchError.getFormattedMessage()}`);
throw researchError;
}
};
// If step is configured for retry, use the retry mechanism
if (wrappedStep.retryable) {
return executeWithRetry(executeFunc, {
maxRetries: creationOptions.maxRetries ?? 3,
retryDelay: creationOptions.retryDelay ?? 1000,
backoffFactor: creationOptions.backoffFactor ?? 2,
onRetry: (attempt, error, delay) => {
stepLogger.warn(`Retry attempt ${attempt} after error: ${error instanceof Error ? error.message : 'Unknown error'}. ` +
`Retrying in ${delay}ms...`);
},
});
}
else {
return executeFunc();
}
},
// Include rollback if the original step had one
rollback: step.rollback
? async (state) => {
stepLogger.info(`Rolling back step`);
try {
// Safe call with null check
if (step.rollback) {
return await step.rollback(state);
}
// If rollback is undefined (shouldn't happen due to the check above), return state unchanged
return state;
}
catch (error) {
stepLogger.error(`Rollback failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
throw error;
}
}
: undefined,
};
return wrappedStep;
}
//# sourceMappingURL=steps.js.map