Skip to content

Commit

Permalink
feat: adapting all handlers to retry handler
Browse files Browse the repository at this point in the history
  • Loading branch information
NarwhalChen committed Jan 13, 2025
1 parent 7c4d783 commit 2daed38
Show file tree
Hide file tree
Showing 12 changed files with 582 additions and 319 deletions.
41 changes: 27 additions & 14 deletions backend/src/build-system/handlers/backend/code-generate/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,8 @@ import { saveGeneratedCode } from 'src/build-system/utils/files';
import * as path from 'path';
import {
formatResponse,
parseGenerateTag,
removeCodeBlockFences,
} from 'src/build-system/utils/strings';

import { NonRetryableError, RetryableError } from 'src/build-system/retry-handler';
/**
* BackendCodeHandler is responsible for generating the backend codebase
* based on the provided sitemap and data mapping documents.
Expand All @@ -21,7 +19,6 @@ export class BackendCodeHandler implements BuildHandler<string> {
/**
* Executes the handler to generate backend code.
* @param context - The builder context containing configuration and utilities.
* @param args - The variadic arguments required for generating the backend code.
* @returns A BuildResult containing the generated code and related data.
*/
async run(context: BuilderContext): Promise<BuildResult<string>> {
Expand All @@ -37,11 +34,18 @@ export class BackendCodeHandler implements BuildHandler<string> {
const sitemapDoc = context.getNodeData('op:UX:SMD');
const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC');
const databaseSchemas = context.getNodeData('op:DATABASE:SCHEMAS');
//TODO: make this backend generate similar as FileGenerateHandler, do file arch, and then generate each backend code
//TODO: backend requirement
const backendRequirementDoc =
context.getNodeData('op:BACKEND:REQ').overview;

if (!sitemapDoc || !datamapDoc || !databaseSchemas) {
return {
success: false,
error: new NonRetryableError(
'Missing required parameters: sitemapDoc, datamapDoc, or databaseSchemas.',
),
};
}

const backendRequirementDoc =
context.getNodeData('op:BACKEND:REQ')?.overview || '';
const currentFile = 'index.js';
const dependencyFile = 'dependencies.json';

Expand All @@ -58,9 +62,6 @@ export class BackendCodeHandler implements BuildHandler<string> {
dependencyFile,
);

// Log the prompt generation
this.logger.debug('Generated backend code prompt.');

try {
// Invoke the language model to generate the backend code
const modelResponse = await context.model.chatSync({
Expand All @@ -70,21 +71,33 @@ export class BackendCodeHandler implements BuildHandler<string> {

const generatedCode = formatResponse(modelResponse);

if (!generatedCode) {
throw new RetryableError('Generated code is empty.');
}

const uuid = context.getGlobalContext('projectUUID');
saveGeneratedCode(path.join(uuid, 'backend', currentFile), generatedCode);

this.logger.debug('Backend code generated and parsed successfully.');

// TODO: return backend api as output
// TODO: return backend API as output
return {
success: true,
data: generatedCode,
};
} catch (error) {
this.logger.error('Error during backend code generation:', error);
if (error instanceof RetryableError) {
this.logger.warn(`Retryable error encountered: ${error.message}`);
return {
success: false,
error,
};
}

this.logger.error('Non-retryable error encountered:', error);
return {
success: false,
error: new Error('Failed to generate backend code.'),
error: new NonRetryableError('Failed to generate backend code.'),
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@ import * as path from 'path';

import { prompts } from './prompt';
import { formatResponse } from 'src/build-system/utils/strings';
import { NonRetryableError, RetryableError } from 'src/build-system/retry-handler';

// TODO(Sma1lboy): we need a better way to handle handler pre requirements
/**
*
* Responsible for review all relate src root file and consider to modify them
* such as package.json, tsconfig.json, .env, etc. in js/ts project
* Responsible for reviewing all related source root files and considering modifications
* such as package.json, tsconfig.json, .env, etc., in JS/TS projects.
* @requires [op:BACKEND:REQ] - BackendRequirementHandler
*/
export class BackendFileReviewHandler implements BuildHandler<string> {
Expand All @@ -33,12 +32,16 @@ export class BackendFileReviewHandler implements BuildHandler<string> {
project name: ${projectName}
project description: ${description},
`;

const backendRequirement = context.getNodeData('op:BACKEND:REQ').overview;
const backendCode = [context.getNodeData('op:BACKEND:CODE')]; // Convert to array for now
const backendCode = [context.getNodeData('op:BACKEND:CODE')];

// 1. Identify files to modify
this.logger.log(`Scanning backend directory: ${backendPath}`);
const files = await fs.readdir(backendPath);
if (!files.length) {
throw new NonRetryableError('No files found in the backend directory.');
}
this.logger.debug(`Found files: ${files.join(', ')}`);

const filePrompt = prompts.identifyBackendFilesToModify(
Expand All @@ -54,16 +57,17 @@ export class BackendFileReviewHandler implements BuildHandler<string> {
});

const filesToModify = this.parseFileIdentificationResponse(modelResponse);
if (!filesToModify.length) {
throw new RetryableError('No files identified for modification.');
}
this.logger.log(`Files to modify: ${filesToModify.join(', ')}`);

// 2. Modify each identified file
for (const fileName of filesToModify) {
const filePath = path.join(backendPath, fileName);
try {
// Read current content
const currentContent = await fs.readFile(filePath, 'utf-8');

// Generate modification prompt
const modificationPrompt = prompts.generateFileModificationPrompt(
fileName,
currentContent,
Expand All @@ -72,20 +76,27 @@ export class BackendFileReviewHandler implements BuildHandler<string> {
backendCode,
);

// Get modified content
const response = await context.model.chatSync({
model: 'gpt-4o-mini',
messages: [{ content: modificationPrompt, role: 'system' }],
});

// Extract new content and write back
const newContent = formatResponse(response);
await fs.writeFile(filePath, newContent, 'utf-8');
if (!newContent) {
throw new RetryableError(
`Failed to generate content for file: ${fileName}.`,
);
}

await fs.writeFile(filePath, newContent, 'utf-8');
this.logger.log(`Successfully modified ${fileName}`);
} catch (error) {
this.logger.error(`Error modifying file ${fileName}:`, error);
throw error;
if (error instanceof RetryableError) {
this.logger.warn(`Retryable error for file ${fileName}: ${error.message}`);
} else {
this.logger.error(`Non-retryable error for file ${fileName}:`, error);
throw error;
}
}
}

Expand All @@ -94,17 +105,33 @@ export class BackendFileReviewHandler implements BuildHandler<string> {
data: `Modified files: ${filesToModify.join(', ')}`,
};
} catch (error) {
this.logger.error('Error during backend file modification:', error);
if (error instanceof RetryableError) {
this.logger.warn(`Retryable error encountered: ${error.message}`);
return {
success: false,
error,
};
}

this.logger.error('Non-retryable error encountered:', error);
return {
success: false,
error: new Error('Failed to modify backend files.'),
error: new NonRetryableError('Failed to modify backend files.'),
};
}
}

parseFileIdentificationResponse(response: string): string[] {
const parsedResponse = JSON.parse(formatResponse(response));
this.logger.log('Parsed file identification response:', parsedResponse);
return parsedResponse;
try {
const parsedResponse = JSON.parse(formatResponse(response));
if (!Array.isArray(parsedResponse)) {
throw new NonRetryableError('File identification response is not an array.');
}
this.logger.log('Parsed file identification response:', parsedResponse);
return parsedResponse;
} catch (error) {
this.logger.error('Error parsing file identification response:', error);
throw new NonRetryableError('Failed to parse file identification response.');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from './prompt';
import { Logger } from '@nestjs/common';
import { removeCodeBlockFences } from 'src/build-system/utils/strings';
import { NonRetryableError, RetryableError } from 'src/build-system/retry-handler';

type BackendRequirementResult = {
overview: string;
Expand All @@ -16,9 +17,8 @@ type BackendRequirementResult = {
packages: Record<string, string>;
};
};

/**
* BackendRequirementHandler is responsible for generating the backend requirements document
* BackendRequirementHandler is responsible for generating the backend requirements document.
* Core Content Generation: API Endpoints, System Overview
*/
export class BackendRequirementHandler
Expand All @@ -42,6 +42,16 @@ export class BackendRequirementHandler
const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC');
const sitemapDoc = context.getNodeData('op:UX:SMD');

if (!dbRequirements || !datamapDoc || !sitemapDoc) {
this.logger.error('Missing required parameters: dbRequirements, datamapDoc, or sitemapDoc');
return {
success: false,
error: new NonRetryableError(
'Missing required parameters: dbRequirements, datamapDoc, or sitemapDoc.',
),
};
}

const overviewPrompt = generateBackendOverviewPrompt(
projectName,
dbRequirements,
Expand All @@ -58,47 +68,32 @@ export class BackendRequirementHandler
model: 'gpt-4o-mini',
messages: [{ content: overviewPrompt, role: 'system' }],
});

if (!backendOverview || backendOverview.trim() === '') {
throw new RetryableError('Generated backend overview is empty.');
}
} catch (error) {
this.logger.error('Error generating backend overview:', error);
if (error instanceof RetryableError) {
this.logger.warn(`Retryable error during backend overview generation: ${error.message}`);
return {
success: false,
error,
};
}

this.logger.error('Non-retryable error generating backend overview:', error);
return {
success: false,
error: new Error('Failed to generate backend overview.'),
error: new NonRetryableError('Failed to generate backend overview.'),
};
}

// // Generate backend implementation details
// const implementationPrompt = generateBackendImplementationPrompt(
// backendOverview,
// language,
// framework,
// );

// let implementationDetails: string;
// try {
// implementationDetails = await context.model.chatSync(
// {
// content: implementationPrompt,
// },
// 'gpt-4o-mini',
// );
// } catch (error) {
// this.logger.error(
// 'Error generating backend implementation details:',
// error,
// );
// return {
// success: false,
// error: new Error('Failed to generate backend implementation details.'),
// };
// }

// Return generated data
return {
success: true,
data: {
overview: removeCodeBlockFences(backendOverview),
// TODO: consider remove implementation
implementation: '',
implementation: '', // Implementation generation skipped
config: {
language,
framework,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,60 @@ import { ModelProvider } from 'src/common/model-provider';
import { prompts } from './prompt';
import { Logger } from '@nestjs/common';
import { removeCodeBlockFences } from 'src/build-system/utils/strings';
import { NonRetryableError, RetryableError } from 'src/build-system/retry-handler';

export class DatabaseRequirementHandler implements BuildHandler<string> {
readonly id = 'op:DATABASE_REQ';
readonly logger = new Logger('DatabaseRequirementHandler');

async run(context: BuilderContext): Promise<BuildResult<string>> {
this.logger.log('Generating Database Requirements Document...');
const projectName =
context.getGlobalContext('projectName') || 'Default Project Name';

const datamapDoc = context.getNodeData('op:UX:DATAMAP:DOC');

if (!datamapDoc) {
this.logger.error('Data mapping document is missing.');
return {
success: false,
error: new NonRetryableError('Missing required parameter: datamapDoc.'),
};
}

const prompt = prompts.generateDatabaseRequirementPrompt(
projectName,
datamapDoc,
);

const model = ModelProvider.getInstance();
const dbRequirementsContent = await model.chatSync({
model: 'gpt-4o-mini',
messages: [{ content: prompt, role: 'system' }],
});
let dbRequirementsContent: string;

try {
dbRequirementsContent = await model.chatSync({
model: 'gpt-4o-mini',
messages: [{ content: prompt, role: 'system' }],
});

if (!dbRequirementsContent || dbRequirementsContent.trim() === '') {
throw new RetryableError('Generated database requirements content is empty.');
}
} catch (error) {
if (error instanceof RetryableError) {
this.logger.warn(`Retryable error encountered: ${error.message}`);
return {
success: false,
error,
};
}

this.logger.error('Non-retryable error encountered:', error);
return {
success: false,
error: new NonRetryableError('Failed to generate database requirements document.'),
};
}

return {
success: true,
data: removeCodeBlockFences(dbRequirementsContent),
Expand Down
Loading

0 comments on commit 2daed38

Please sign in to comment.