Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add uuid npm dependency for unique identifier generation #48

Merged
merged 2 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
37 changes: 28 additions & 9 deletions backend/src/build-system/context.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -14,12 +15,19 @@ export class BuilderContext {
failed: new Set(),
waiting: new Set(),
};

private logger;
private data: Record<string, any> = {};
// Store the results of the nodes
private results: Map<string, BuildResult> = 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 {
Expand All @@ -41,7 +49,7 @@ export class BuilderContext {
return null;
}

async run(nodeId: string): Promise<BuildResult> {
async run(nodeId: string, args: unknown | undefined): Promise<BuildResult> {
const node = this.findNode(nodeId);
if (!node) {
throw new Error(`Node not found: ${nodeId}`);
Expand All @@ -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);
Expand All @@ -76,17 +88,24 @@ export class BuilderContext {
return this.data[key];
}

private async executeNode(node: BuildNode): Promise<BuildResult> {
getResult(nodeId: string): BuildResult | undefined {
return this.results.get(nodeId);
}

private async executeNode(
node: BuildNode,
args: unknown,
): Promise<BuildResult> {
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);
}
}
34 changes: 24 additions & 10 deletions backend/src/build-system/executor.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,44 @@
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<void> {
try {
if (this.context.getState().completed.has(node.id)) {
return;
}

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<void> {
console.log(`Executing build step: ${step.id}`);
this.logger.log(`Executing build step: ${step.id}`);

if (step.parallel) {
let remainingNodes = [...step.nodes];
Expand Down Expand Up @@ -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`,
);
}
Expand All @@ -93,7 +107,7 @@ export class BuildSequenceExecutor {
}

async executeSequence(sequence: BuildSequence): Promise<void> {
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);
Expand All @@ -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(', ')}`,
Expand All @@ -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());
}
}
26 changes: 5 additions & 21 deletions backend/src/build-system/node/product_requirements_document/prd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BuildResult> {
console.log('Generating PRD...');
this.logger.log('Generating PRD...');

// Extract project data from the context
const projectName =
Expand All @@ -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,
Expand All @@ -39,20 +34,9 @@ export class PRDHandler implements BuildHandler {

private async generatePRDFromLLM(prompt: string): Promise<string> {
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;
}
}
11 changes: 10 additions & 1 deletion backend/src/build-system/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ModelProvider } from 'src/common/model-provider';
import { BuilderContext } from './context';

export type BuildNodeType =
Expand Down Expand Up @@ -74,6 +75,14 @@ export interface BuildExecutionState {
}

export interface BuildHandler {
// Unique identifier for the handler
id: string;
run(context: BuilderContext): Promise<BuildResult>;

/**
*
* @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<BuildResult>;
}
31 changes: 18 additions & 13 deletions backend/src/common/model-provider/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ export class ModelProvider {
private readonly config: ModelProviderConfig,
) {}

async chatSync(
input: ChatInput | string,
model: string,
chatId?: string,
): Promise<string> {
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,
Expand Down Expand Up @@ -199,19 +217,6 @@ export class ModelProvider {
}
}

async chunkSync(chatStream: AsyncIterableIterator<any>): Promise<string> {
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);

Expand Down
7 changes: 6 additions & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading