diff --git a/backend/package.json b/backend/package.json index 92ae3f9..f78855f 100644 --- a/backend/package.json +++ b/backend/package.json @@ -46,7 +46,8 @@ "rxjs": "^7.8.1", "sqlite3": "^5.1.7", "subscriptions-transport-ws": "^0.11.0", - "typeorm": "^0.3.20" + "typeorm": "^0.3.20", + "uuid": "^10.0.0" }, "devDependencies": { "@eslint/eslintrc": "^3.1.0", diff --git a/backend/src/build-system/context.ts b/backend/src/build-system/context.ts index 2d00c19..fda18c8 100644 --- a/backend/src/build-system/context.ts +++ b/backend/src/build-system/context.ts @@ -1,11 +1,12 @@ +import { ModelProvider } from 'src/common/model-provider'; import { BuildHandlerManager } from './hanlder-manager'; import { BuildExecutionState, BuildNode, BuildResult, BuildSequence, - BuildStep, } from './types'; +import { Logger } from '@nestjs/common'; export class BuilderContext { private state: BuildExecutionState = { @@ -14,12 +15,19 @@ export class BuilderContext { failed: new Set(), waiting: new Set(), }; - + private logger; private data: Record = {}; + // Store the results of the nodes + private results: Map = new Map(); private handlerManager: BuildHandlerManager; + public model: ModelProvider; - constructor(private sequence: BuildSequence) { + constructor( + private sequence: BuildSequence, + id: string, + ) { this.handlerManager = BuildHandlerManager.getInstance(); + new Logger(`builder-context-${id}`); } canExecute(nodeId: string): boolean { @@ -41,7 +49,7 @@ export class BuilderContext { return null; } - async run(nodeId: string): Promise { + async run(nodeId: string, args: unknown | undefined): Promise { const node = this.findNode(nodeId); if (!node) { throw new Error(`Node not found: ${nodeId}`); @@ -53,9 +61,13 @@ export class BuilderContext { try { this.state.pending.add(nodeId); - const result = await this.executeNode(node); + const result = await this.executeNode(node, args); this.state.completed.add(nodeId); this.state.pending.delete(nodeId); + + // Store the result for future use + this.results.set(nodeId, result); + return result; } catch (error) { this.state.failed.add(nodeId); @@ -76,17 +88,24 @@ export class BuilderContext { return this.data[key]; } - private async executeNode(node: BuildNode): Promise { + getResult(nodeId: string): BuildResult | undefined { + return this.results.get(nodeId); + } + + private async executeNode( + node: BuildNode, + args: unknown, + ): Promise { if (process.env.NODE_ENV === 'test') { - console.log(`[TEST] Executing node: ${node.id}`); + this.logger.log(`[TEST] Executing node: ${node.id}`); return { success: true, data: { nodeId: node.id } }; } - console.log(`Executing node: ${node.id}`); + this.logger.log(`Executing node: ${node.id}`); const handler = this.handlerManager.getHandler(node.id); if (!handler) { throw new Error(`No handler found for node: ${node.id}`); } - return handler.run(this); + return handler.run(this, args); } } diff --git a/backend/src/build-system/executor.ts b/backend/src/build-system/executor.ts index adb7a67..edd252e 100644 --- a/backend/src/build-system/executor.ts +++ b/backend/src/build-system/executor.ts @@ -1,9 +1,12 @@ +import { Logger } from '@nestjs/common'; import { BuilderContext } from './context'; import { BuildNode, BuildSequence, BuildStep } from './types'; +import { v4 as uuidv4 } from 'uuid'; export class BuildSequenceExecutor { constructor(private context: BuilderContext) {} + private logger: Logger = new Logger(`BuildSequenceExecutor-${uuidv4()}`); private async executeNode(node: BuildNode): Promise { try { if (this.context.getState().completed.has(node.id)) { @@ -11,20 +14,31 @@ export class BuildSequenceExecutor { } if (!this.context.canExecute(node.id)) { - console.log(`Waiting for dependencies: ${node.requires?.join(', ')}`); - await new Promise((resolve) => setTimeout(resolve, 100)); // 添加小延迟 + this.logger.log( + `Waiting for dependencies: ${node.requires?.join(', ')}`, + ); + await new Promise((resolve) => setTimeout(resolve, 100)); return; } - await this.context.run(node.id); + const dependenciesResults = node.requires?.map((depId) => + this.context.getResult(depId), + ); + + this.logger.log( + `Executing node ${node.id} with dependencies:`, + dependenciesResults, + ); + + await this.context.run(node.id, dependenciesResults); } catch (error) { - console.error(`Error executing node ${node.id}:`, error); + this.logger.error(`Error executing node ${node.id}:`, error); throw error; } } private async executeStep(step: BuildStep): Promise { - console.log(`Executing build step: ${step.id}`); + this.logger.log(`Executing build step: ${step.id}`); if (step.parallel) { let remainingNodes = [...step.nodes]; @@ -84,7 +98,7 @@ export class BuildSequenceExecutor { if (!this.context.getState().completed.has(node.id)) { // TODO: change to error log - console.warn( + this.logger.warn( `Failed to execute node ${node.id} after ${maxRetries} attempts`, ); } @@ -93,7 +107,7 @@ export class BuildSequenceExecutor { } async executeSequence(sequence: BuildSequence): Promise { - console.log(`Starting build sequence: ${sequence.id}`); + this.logger.log(`Starting build sequence: ${sequence.id}`); for (const step of sequence.steps) { await this.executeStep(step); @@ -104,7 +118,7 @@ export class BuildSequenceExecutor { if (incompletedNodes.length > 0) { // TODO: change to error log - console.warn( + this.logger.warn( `Step ${step.id} failed to complete nodes: ${incompletedNodes .map((n) => n.id) .join(', ')}`, @@ -113,7 +127,7 @@ export class BuildSequenceExecutor { } } - console.log(`Build sequence completed: ${sequence.id}`); - console.log('Final state:', this.context.getState()); + this.logger.log(`Build sequence completed: ${sequence.id}`); + this.logger.log('Final state:', this.context.getState()); } } diff --git a/backend/src/build-system/node/product_requirements_document/prd.ts b/backend/src/build-system/node/product_requirements_document/prd.ts index a67e7f0..e65052f 100644 --- a/backend/src/build-system/node/product_requirements_document/prd.ts +++ b/backend/src/build-system/node/product_requirements_document/prd.ts @@ -2,15 +2,13 @@ import { BuildHandler, BuildResult } from 'src/build-system/types'; import { BuilderContext } from 'src/build-system/context'; import { prompts } from './prompt/prompt'; import { ModelProvider } from 'src/common/model-provider'; -import { StreamStatus } from 'src/chat/chat.model'; -import * as fs from 'fs'; -import * as path from 'path'; +import { Logger } from '@nestjs/common'; export class PRDHandler implements BuildHandler { readonly id = 'op:PRD::STATE:GENERATE'; - + readonly logger: Logger = new Logger('PRDHandler'); async run(context: BuilderContext): Promise { - console.log('Generating PRD...'); + this.logger.log('Generating PRD...'); // Extract project data from the context const projectName = @@ -28,9 +26,6 @@ export class PRDHandler implements BuildHandler { // Send the prompt to the LLM server and process the response const prdContent = await this.generatePRDFromLLM(prompt); - // Save the PRD content to context for further use - context.setData('prdDocument', prdContent); - return { success: true, data: prdContent, @@ -39,20 +34,9 @@ export class PRDHandler implements BuildHandler { private async generatePRDFromLLM(prompt: string): Promise { const modelProvider = ModelProvider.getInstance(); - const model = 'gpt-3.5-turbo'; - - // Call the chat method with the model specified - const chatStream = modelProvider.chat( - { - content: prompt, - }, - model, - ); // Pass the model here - - const prdContent = modelProvider.chunkSync(chatStream); - - console.log('Received full PRD content from LLM server.'); + const prdContent = await modelProvider.chatSync({ content: prompt }, model); + this.logger.log('Received full PRD content from LLM server.'); return prdContent; } } diff --git a/backend/src/build-system/types.ts b/backend/src/build-system/types.ts index 47a88bc..cbf7bc7 100644 --- a/backend/src/build-system/types.ts +++ b/backend/src/build-system/types.ts @@ -1,3 +1,4 @@ +import { ModelProvider } from 'src/common/model-provider'; import { BuilderContext } from './context'; export type BuildNodeType = @@ -74,6 +75,14 @@ export interface BuildExecutionState { } export interface BuildHandler { + // Unique identifier for the handler id: string; - run(context: BuilderContext): Promise; + + /** + * + * @param context the context object for the build + * @param model model provider for the build + * @param args the request arguments + */ + run(context: BuilderContext, args: unknown): Promise; } diff --git a/backend/src/common/model-provider/index.ts b/backend/src/common/model-provider/index.ts index c080a00..1f7f6b4 100644 --- a/backend/src/common/model-provider/index.ts +++ b/backend/src/common/model-provider/index.ts @@ -49,6 +49,24 @@ export class ModelProvider { private readonly config: ModelProviderConfig, ) {} + async chatSync( + input: ChatInput | string, + model: string, + chatId?: string, + ): Promise { + const chatStream = this.chat(input, model, chatId); + let content = ''; + for await (const chunk of chatStream) { + if (chunk.status === StreamStatus.STREAMING) { + content += chunk.choices + .map((choice) => choice.delta?.content || '') + .join(''); + } + } + this.logger.log('Aggregated content from chat stream:', content); + return content; + } + chat( input: ChatInput | string, model: string, @@ -199,19 +217,6 @@ export class ModelProvider { } } - async chunkSync(chatStream: AsyncIterableIterator): Promise { - let aggregatedContent = ''; - for await (const chunk of chatStream) { - if (chunk.status === StreamStatus.STREAMING) { - aggregatedContent += chunk.choices - .map((choice) => choice.delta?.content || '') - .join(''); - } - } - this.logger.log('Aggregated content from chat stream:', aggregatedContent); - return aggregatedContent; - } - private startChat(input: ChatInput, model: string, chatId?: string) { const payload = this.createRequestPayload(input, model, chatId); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbe24b1..bfb76d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -102,6 +102,9 @@ importers: typeorm: specifier: ^0.3.20 version: 0.3.20(sqlite3@5.1.7)(ts-node@10.9.2) + uuid: + specifier: ^10.0.0 + version: 10.0.0 devDependencies: '@eslint/eslintrc': specifier: ^3.1.0 @@ -6297,6 +6300,7 @@ packages: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + requiresBuild: true dependencies: delegates: 1.0.0 readable-stream: 3.6.2 @@ -6504,7 +6508,7 @@ packages: resolution: {integrity: sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==} dependencies: follow-redirects: 1.15.9(debug@4.3.7) - form-data: 4.0.0 + form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -8912,6 +8916,7 @@ packages: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} deprecated: This package is no longer supported. + requiresBuild: true dependencies: aproba: 2.0.0 color-support: 1.1.3