diff --git a/agent.py b/agent.py index 7e3ab7e..5418e30 100644 --- a/agent.py +++ b/agent.py @@ -2,17 +2,83 @@ from orchestrate import execute_pipeline import helpers as h import time +import wandb +import datetime +from wandb.sdk.data_types.trace_tree import Trace +import globals +import yaml +import json +import pandas as pd + +# Load the configuration +with open('config.yml', 'r') as f: + config = yaml.safe_load(f) + +# Set the wandb_enabled flag +wandb_enabled = config['wandb_enabled'] +pipeline_path = "pipelines/" + config['pipeline'] + ".yml" + + +if wandb_enabled: + wandb.init(project="OrchestrAI") + wandb.config.wandb_enabled = wandb_enabled def main(): print("\033[95m ------- Welcome to OrchestrAI ------\033[00m") time.sleep(1) - # Load the desired pipeline - pipeline_path = "pipeline.yml" pipeline = h.load_pipeline(pipeline_path) + # Get the name of the pipeline file + file_name = pipeline_path.split("/")[-1].split(".")[0] + + + if wandb_enabled: + globals.agent_start_time_ms = round(datetime.datetime.now().timestamp() * 1000) + + # create a root span for the pipeline + root_span = Trace( + name="Pipeline", + kind="agent", + start_time_ms=globals.agent_start_time_ms, + metadata={"pipeline_name": file_name}, + ) + + #The Agent calls into a LLMChain.. + globals.chain_span = Trace( + name="LLMChain", + kind="chain", + start_time_ms=globals.agent_start_time_ms, + ) + + root_span.add_child(globals.chain_span) + # Execute the pipeline - execute_pipeline(pipeline) + try: + execute_pipeline(pipeline) + except Exception as e: + print(f"An error occurred during pipeline execution: {e}") + finally: + if wandb_enabled: + # Log the root span to Weights & Biases + agent_end_time_ms = round(datetime.datetime.now().timestamp() * 1000) + root_span._span.end_time_ms = agent_end_time_ms + + root_span.log(name="pipeline_trace") + wandb.finish() + + + # Open and load the JSON file + with open('memory_log.json', 'r') as f: + data = json.load(f) + + # Convert the JSON data to a pandas DataFrame + df = pd.DataFrame(data) + + # Log the DataFrame to Weights & Biases + wandb.log({"memory_log": wandb.Table(dataframe=df)}) + + print("\033[95m ------- OrchestrAI finished ------\033[00m") if __name__ == "__main__": main() diff --git a/ai.py b/ai.py index 1e1c3c0..7eae5e2 100644 --- a/ai.py +++ b/ai.py @@ -1,47 +1,104 @@ import openai -openai.api_key = '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' -import json +import yaml + +# Load the configuration +with open('config.yml', 'r') as f: + config = yaml.safe_load(f) + +# Set the wandb_enabled flag +wandb_enabled = config['wandb_enabled'] +openai.api_key = config['openai_api_key'] + +# Check if the API key works +try: + openai.Model.list() +except openai.error.AuthenticationError: + raise ValueError("Invalid OpenAI API key") + +from wandb.sdk.data_types.trace_tree import Trace +import datetime +import helpers as h +import globals +import yaml class AI: - def __init__(self, system="", model = 'gpt-4', openai=openai): - self.system = system + def __init__(self, module_name, model = 'gpt-4', temperature=0.7, openai=openai): self.model = model self.openai = openai - self.messages = [{"role": "system", "content": system}] + self.temperature = temperature + self.module_name = module_name + self.system = h.load_system_prompt(module_name) + self.messages = [{"role": "system", "content": self.system}] def generate_response(self, prompt): self.messages.append({"role": "user", "content": prompt}) - response = self.openai.ChatCompletion.create( - model=self.model, - stream=True, - messages=self.messages, - ) - chat = [] - for chunk in response: - delta = chunk["choices"][0]["delta"] - msg = delta.get("content", "") - print(msg, end="") - chat.append(msg) + try: + response = self.openai.ChatCompletion.create( + model=self.model, + stream=True, + messages=self.messages, + temperature=self.temperature, + ) + + chat = [] + token_count = 0 + for chunk in response: + delta = chunk["choices"][0]["delta"] + msg = delta.get("content", "") + print(msg, end="") + chat.append(msg) + token_count += len(msg.split()) # estimate token usage + + print() + + response_text = "".join(chat) + llm_end_time_ms = round(datetime.datetime.now().timestamp() * 1000) # logged in milliseconds + status_code="success" + status_message=None, + token_usage = {"total_tokens": token_count} + + except Exception as e: + llm_end_time_ms = round(datetime.datetime.now().timestamp() * 1000) # logged in milliseconds + status_code="error" + status_message=str(e) + response_text = "" + token_usage = {} + + if wandb_enabled: + # calculate the runtime of the LLM + runtime = llm_end_time_ms - globals.agent_start_time_ms + # create a child span in wandb + llm_span = Trace( + name="child_span", + kind="llm", # kind can be "llm", "chain", "agent" or "tool" + status_code=status_code, + status_message=status_message, + metadata={"temperature": self.temperature, + "token_usage": token_usage, + "runtime_ms": runtime, + "module_name": self.module_name, + "model_name": self.model}, + start_time_ms=globals.agent_start_time_ms, + end_time_ms=llm_end_time_ms, + inputs={"system_prompt": self.system, "query": prompt}, + outputs={"response": response_text} + ) + + # add the child span to the root span + globals.chain_span.add_child(llm_span) - print() + # update the end time of the Chain span + globals.chain_span.add_inputs_and_outputs( + inputs={"query": prompt}, + outputs={"response": response_text}) - response_text = "".join(chat) + # update the Chain span's end time + globals.chain_span._span.end_time_ms = llm_end_time_ms + # log the child span to wandb + llm_span.log(name="pipeline_trace") + self.messages.append({"role": "assistant", "content": response_text}) - return response_text, self.messages - - def generate_image(self, prompt, n=1, size="1024x1024", response_format="url"): - """Generate an image using DALL·E given a prompt. - - Arguments: - prompt (str): A text description of the desired image(s). - n (int, optional): The number of images to generate. Defaults to 1. - size (str, optional): The size of the generated images. Defaults to "1024x1024". - response_format (str, optional): The format in which the generated images are returned. Defaults to "url". - - Returns: - dict: The response from the OpenAI API. - """ - return openai.Image.create(prompt=prompt, n=n, size=size, response_format=response_format) + return response_text, self.messages \ No newline at end of file diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..c867816 --- /dev/null +++ b/config.yml @@ -0,0 +1,3 @@ +openai_api_key: 'OPENAI_API_KEY' +wandb_enabled: false +pipeline: "engineering_pipeline" \ No newline at end of file diff --git a/helpers.py b/helpers.py index 90b7a9b..269bb61 100644 --- a/helpers.py +++ b/helpers.py @@ -3,6 +3,7 @@ import subprocess import sys import yaml +import matplotlib.pyplot as plt def load_pipeline(file_path): """Load a pipeline configuration from a YAML file.""" @@ -108,3 +109,25 @@ def extract_codebase(directory='generated_code', ignore_list=[]): pass return "\n".join(result_content) + +def visualize_pipeline(nx, G): + # Increase distance between nodes by setting k parameter + pos = nx.spring_layout(G, seed=42, k=2) + + # Get module names as labels + labels = nx.get_node_attributes(G, 'module') + + # Draw the nodes + nx.draw_networkx_nodes(G, pos, node_color='skyblue', node_size=300) + + # Draw the edges with arrows + nx.draw_networkx_edges(G, pos, arrowstyle="->", arrowsize=10, edge_color="gray") + + # Draw the labels for the nodes with reduced font size + nx.draw_networkx_labels(G, pos, labels, font_size=8) + + # Draw the labels for the edges with reduced font size + edge_labels = nx.get_edge_attributes(G, 'label') + nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=6) + + plt.savefig("pipeline.png", dpi=300, bbox_inches='tight') \ No newline at end of file diff --git a/memory_log.json b/memory_log.json new file mode 100644 index 0000000..1ee9a34 --- /dev/null +++ b/memory_log.json @@ -0,0 +1 @@ +[{"module": "start", "prompt": "prime number generator up to 50", "response": null, "model": null, "timestamp": "2023-08-27T15:35:00.881129"}, {"module": "code_planner", "prompt": "\n Additional User Inputprime number generator up to 50\n Additional User InputUse only python.", "response": "## Program Specification\n\nThe program is a Prime Number Generator up to 50. It will generate all prime numbers up to a given limit, in this case, 50. A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. \n\n### Features\n\n1. The program will generate all prime numbers up to 50.\n2. The program will print the generated prime numbers.\n3. The program will handle exceptions gracefully and provide meaningful error messages.\n\n### Detailed Description\n\nThe program starts by initializing a list of prime numbers with the first prime number, 2. It then iterates over all odd numbers up to the given limit, checking for each number if it is prime. A number is prime if it is not divisible by any of the previously found prime numbers. If a number is found to be prime, it is added to the list of prime numbers. Finally, the list of prime numbers is printed.\n\nThe program uses the Sieve of Eratosthenes algorithm for generating prime numbers, which is an efficient algorithm for finding all primes smaller than a given number n.\n\n## Code Specification\n\n### Classes, Functions, and Methods\n\n1. `is_prime(n, primes)`: This function checks if a given number `n` is prime. It takes as input a number `n` and a list of known prime numbers `primes` and returns a boolean indicating whether `n` is prime or not.\n\n2. `generate_primes(limit)`: This function generates all prime numbers up to a given limit. It takes as input a limit and returns a list of prime numbers up to that limit.\n\n3. `main()`: This function is the entry point of the program. It calls the `generate_primes` function with the limit 50 and prints the returned list of prime numbers.\n\n### main.py\n\n```\ndef is_prime(n, primes):\n # checks if a number is prime\n\ndef generate_primes(limit):\n # generates all prime numbers up to a limit\n\ndef main():\n # the entry point of the program\n```\n\nThe scripts will not be nested within a further directory. Running the full code should always be possible by running main.py. This specification will be used later as the basis for the implementation.", "model": "gpt-4", "timestamp": "2023-08-27T15:35:37.290988"}, {"module": "engineer", "prompt": "## Program Specification\n\nThe program is a Prime Number Generator up to 50. It will generate all prime numbers up to a given limit, in this case, 50. A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. \n\n### Features\n\n1. The program will generate all prime numbers up to 50.\n2. The program will print the generated prime numbers.\n3. The program will handle exceptions gracefully and provide meaningful error messages.\n\n### Detailed Description\n\nThe program starts by initializing a list of prime numbers with the first prime number, 2. It then iterates over all odd numbers up to the given limit, checking for each number if it is prime. A number is prime if it is not divisible by any of the previously found prime numbers. If a number is found to be prime, it is added to the list of prime numbers. Finally, the list of prime numbers is printed.\n\nThe program uses the Sieve of Eratosthenes algorithm for generating prime numbers, which is an efficient algorithm for finding all primes smaller than a given number n.\n\n## Code Specification\n\n### Classes, Functions, and Methods\n\n1. `is_prime(n, primes)`: This function checks if a given number `n` is prime. It takes as input a number `n` and a list of known prime numbers `primes` and returns a boolean indicating whether `n` is prime or not.\n\n2. `generate_primes(limit)`: This function generates all prime numbers up to a given limit. It takes as input a limit and returns a list of prime numbers up to that limit.\n\n3. `main()`: This function is the entry point of the program. It calls the `generate_primes` function with the limit 50 and prints the returned list of prime numbers.\n\n### main.py\n\n```\ndef is_prime(n, primes):\n # checks if a number is prime\n\ndef generate_primes(limit):\n # generates all prime numbers up to a limit\n\ndef main():\n # the entry point of the program\n```\n\nThe scripts will not be nested within a further directory. Running the full code should always be possible by running main.py. This specification will be used later as the basis for the implementation.\n Additional User Input", "response": "main.py\n```python\ndef is_prime(n, primes):\n for prime in primes:\n if n % prime == 0:\n return False\n return True\n\ndef generate_primes(limit):\n primes = [2]\n for num in range(3, limit+1, 2):\n if is_prime(num, primes):\n primes.append(num)\n return primes\n\ndef main():\n primes = generate_primes(50)\n print(primes)\n\nif __name__ == \"__main__\":\n main()\n```\nrequirements.txt\n```txt\npython\n```", "model": "gpt-4", "timestamp": "2023-08-27T15:35:47.496752"}, {"module": "modify_codebase", "prompt": "--- File Name: game.py ---\nimport pygamefrom paddle import Paddlefrom ball import Ballfrom brick import Brickclass Game: def __init__(self): pygame.init() self.window = pygame.display.set_mode((800, 600)) self.running = True self.score = 0 self.paddle = Paddle((400, 550)) self.bricks = [Brick((x, y)) for x in range(50, 750, 100) for y in range(50, 250, 50)] self.ball = Ball((400, 300), self.paddle, self.bricks) # Moved the initialization of self.ball after self.bricks def run(self): while self.running: self.update() self.draw() pygame.display.update() pygame.time.delay(30) def update(self): for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False self.ball.move() if len(self.bricks) == 0 or self.ball.rect.bottom > 600: self.end_game() def draw(self): self.window.fill((0, 0, 0)) self.paddle.draw(self.window) self.ball.draw(self.window) for brick in self.bricks: brick.draw(self.window) def end_game(self): print(f'Game Over! Your score: {self.score}') self.running = False\n--- File Name: paddle.py ---\nimport pygameclass Paddle: def __init__(self, position): self.rect = pygame.Rect(position[0], position[1], 100, 20) def move(self, direction): if direction == 'left': self.rect.move_ip(-10, 0) else: self.rect.move_ip(10, 0) def draw(self, window): pygame.draw.rect(window, (255, 255, 255), self.rect)\n--- File Name: collision.py ---\ndef detect_collision(ball, obj): return ball.x + ball.radius > obj.x and ball.x - ball.radius < obj.x + obj.width and ball.y + ball.radius > obj.y and ball.y - ball.radius < obj.y + obj.height\n--- File Name: ball.py ---\nimport pygameclass Ball: def __init__(self, position, paddle, bricks): self.rect = pygame.Rect(position[0], position[1], 10, 10) self.speed = [5, 5] self.paddle = paddle self.bricks = bricks def move(self): self.rect.move_ip(self.speed) if self.rect.left < 0 or self.rect.right > 800: self.speed[0] = -self.speed[0] if self.rect.top < 0: self.speed[1] = -self.speed[1] if self.rect.colliderect(self.paddle.rect): self.speed[1] = -self.speed[1] for brick in self.bricks: if self.rect.colliderect(brick.rect): self.speed[1] = -self.speed[1] self.bricks.remove(brick) def draw(self, window): pygame.draw.rect(window, (255, 255, 255), self.rect)\n--- File Name: main.py ---\ndef is_prime(n, primes): for prime in primes: if n % prime == 0: return False return Truedef generate_primes(limit): primes = [2] for num in range(3, limit+1, 2): if is_prime(num, primes): primes.append(num) return primesdef main(): primes = generate_primes(50) print(primes)if __name__ == \"__main__\": main()\n--- File Name: brick.py ---\nimport pygameclass Brick: def __init__(self, position): self.rect = pygame.Rect(position[0], position[1], 80, 20) def draw(self, window): pygame.draw.rect(window, (255, 255, 255), self.rect)\n Additional User Input\n -- User Instructions --Make it to 51", "response": "main.py\n```python\ndef is_prime(n, primes): \n for prime in primes: \n if n % prime == 0: \n return False \n return True\n\ndef generate_primes(limit): \n primes = [2] \n for num in range(3, limit+1, 2): \n if is_prime(num, primes): \n primes.append(num) \n return primes\n\ndef main(): \n primes = generate_primes(51) \n print(primes)\n\nif __name__ == \"__main__\": \n main()\n```", "model": "gpt-4", "timestamp": "2023-08-27T15:36:06.661836"}, {"module": "create_readme", "prompt": "--- File Name: game.py ---\nimport pygamefrom paddle import Paddlefrom ball import Ballfrom brick import Brickclass Game: def __init__(self): pygame.init() self.window = pygame.display.set_mode((800, 600)) self.running = True self.score = 0 self.paddle = Paddle((400, 550)) self.bricks = [Brick((x, y)) for x in range(50, 750, 100) for y in range(50, 250, 50)] self.ball = Ball((400, 300), self.paddle, self.bricks) # Moved the initialization of self.ball after self.bricks def run(self): while self.running: self.update() self.draw() pygame.display.update() pygame.time.delay(30) def update(self): for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False self.ball.move() if len(self.bricks) == 0 or self.ball.rect.bottom > 600: self.end_game() def draw(self): self.window.fill((0, 0, 0)) self.paddle.draw(self.window) self.ball.draw(self.window) for brick in self.bricks: brick.draw(self.window) def end_game(self): print(f'Game Over! Your score: {self.score}') self.running = False\n--- File Name: paddle.py ---\nimport pygameclass Paddle: def __init__(self, position): self.rect = pygame.Rect(position[0], position[1], 100, 20) def move(self, direction): if direction == 'left': self.rect.move_ip(-10, 0) else: self.rect.move_ip(10, 0) def draw(self, window): pygame.draw.rect(window, (255, 255, 255), self.rect)\n--- File Name: collision.py ---\ndef detect_collision(ball, obj): return ball.x + ball.radius > obj.x and ball.x - ball.radius < obj.x + obj.width and ball.y + ball.radius > obj.y and ball.y - ball.radius < obj.y + obj.height\n--- File Name: ball.py ---\nimport pygameclass Ball: def __init__(self, position, paddle, bricks): self.rect = pygame.Rect(position[0], position[1], 10, 10) self.speed = [5, 5] self.paddle = paddle self.bricks = bricks def move(self): self.rect.move_ip(self.speed) if self.rect.left < 0 or self.rect.right > 800: self.speed[0] = -self.speed[0] if self.rect.top < 0: self.speed[1] = -self.speed[1] if self.rect.colliderect(self.paddle.rect): self.speed[1] = -self.speed[1] for brick in self.bricks: if self.rect.colliderect(brick.rect): self.speed[1] = -self.speed[1] self.bricks.remove(brick) def draw(self, window): pygame.draw.rect(window, (255, 255, 255), self.rect)\n--- File Name: main.py ---\ndef is_prime(n, primes): for prime in primes: if n % prime == 0: return False return Truedef generate_primes(limit): primes = [2] for num in range(3, limit+1, 2): if is_prime(num, primes): primes.append(num) return primesdef main(): primes = generate_primes(51) print(primes)if __name__ == \"__main__\": main()\n--- File Name: brick.py ---\nimport pygameclass Brick: def __init__(self, position): self.rect = pygame.Rect(position[0], position[1], 80, 20) def draw(self, window): pygame.draw.rect(window, (255, 255, 255), self.rect)\n Additional User Input", "response": "# PyBricks - A Simple Brick Breaker Game in Python\n\n## Description\n\nPyBricks is a simple and fun brick breaker game implemented in Python. The game is designed using Pygame, a cross-platform set of Python modules designed for writing video games. This game features a paddle and a ball, where the player controls the paddle to bounce the ball and break bricks displayed on the screen.\n\n### Components\n\nThe game consists of the following components:\n\n1. **Game**: This is the main game class that manages the game loop, events and updates the game state.\n2. **Paddle**: This class represents the paddle controlled by the player.\n3. **Ball**: This class represents the ball that bounces around the screen.\n4. **Brick**: This class represents the bricks that the player must break.\n\nThe game also includes a `collision.py` module to handle collision detection.\n\n## Usage\n\nTo play the game, you will need to have Python and Pygame installed on your system. You can start the game by running the `game.py` file.\n\n```bash\npython game.py\n```\n\n### Controls\n\n- Left Arrow: Move the paddle to the left.\n- Right Arrow: Move the paddle to the right.\n\nThe ball will move automatically, and the game ends when all bricks are broken or if the ball touches the bottom of the screen.\n\n## Code Overview\n\n- `game.py`: This is the main module that contains the game loop and handles game events. It creates instances of the Paddle, Ball, and Bricks classes, and controls the game logic.\n- `paddle.py`: This module defines the Paddle class with methods for drawing and moving the paddle.\n- `ball.py`: This module defines the Ball class with methods for moving the ball and detecting collisions with the paddle and bricks.\n- `brick.py`: This module defines the Brick class with a method for drawing bricks.\n- `collision.py`: This module contains a function for detecting collisions between the ball and other objects.\n- `main.py`: This module is unrelated to the game and seems to contain a utility function for generating prime numbers.\n\n## End Game\n\nThe game will print `Game Over! Your score: {self.score}` and stop when all bricks are broken or if the ball touches the bottom of the screen.\n\n## Note\n\nThe `main.py` file seems to be unrelated to the game and contains a function to generate prime numbers. It might be part of another project or a utility function for a feature not yet implemented in the game.", "model": "gpt-4", "timestamp": "2023-08-27T15:36:50.008021"}] \ No newline at end of file diff --git a/modules.py b/modules.py index 543df30..e9e5c0a 100644 --- a/modules.py +++ b/modules.py @@ -22,8 +22,7 @@ def human_intervention(module_input): def task_planner(prompt): module_name = "task_planner" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') response, messages = ai.generate_response(prompt) logger.log_action(module_name, prompt, response, 'gpt-4') @@ -31,8 +30,7 @@ def task_planner(prompt): def scrutinizer(prompt): module_name = "scrutinizer" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') response, messages = ai.generate_response(prompt) logger.log_action(module_name, prompt, response, 'gpt-4') @@ -40,8 +38,7 @@ def scrutinizer(prompt): def enhancer(prompt): module_name = "enhancer" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') response, messages = ai.generate_response(prompt) logger.log_action(module_name, prompt, response, 'gpt-4') @@ -49,8 +46,7 @@ def enhancer(prompt): def code_planner(prompt): module_name = "code_planner" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') print("\033[93mPlanning code...\033[00m") response, messages = ai.generate_response(prompt) @@ -59,8 +55,7 @@ def code_planner(prompt): def engineer(prompt): module_name = "engineer" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') print("\033[93mGenerating code...\033[00m") response, messages = ai.generate_response(prompt) @@ -80,11 +75,11 @@ def engineer(prompt): def debugger(codebase): module_name = "debugger" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-3.5-turbo-16k') # Add a debug_attempt counter debug_attempt = 0 + max_attempts = 3 + attempts_left = max_attempts while True: exit_code, error_msg = h.run_main() @@ -99,25 +94,29 @@ def debugger(codebase): print(f"Error encountered: {error_msg}") # Check if we have made more than 3 debugging attempts - if debug_attempt >= 3: + if debug_attempt >= max_attempts: print("\033[91mDebugging has taken more than 3 attempts.\033[00m") print("1: Invoke human intervention") - print("2: End the pipeline early") + print("2: End debugging and move to next module") choice = input("Please select an option (1 or 2): ") - + if choice == "1": + debug_attempt = 0 + attempts_left = max_attempts print("Please provide input for human intervention:") human_input = input() prompt = codebase + "\n The error encountered is: \n" + error_msg + "\n Human Intervention: \n" + human_input elif choice == "2": - raise Exception("Auto Debugging Failed") + break # End debugging and move to next module else: print("Invalid choice. Please try again.") continue - + else: - print("\033[95mDebugging code...\033[00m") + print("\033[95mDebugging codebase...\033[00m") + print(f"\033[96m{attempts_left} attempts left\033[00m") prompt = codebase + "\n The error encountered is: \n" + error_msg + ai = AI(module_name, model='gpt-3.5-turbo-16k') debug_response, messages = ai.generate_response(prompt) logger.log_action(module_name, prompt, debug_response, 'gpt-4') debugged_code = h.parse_chat(debug_response) @@ -132,12 +131,14 @@ def debugger(codebase): # Increment the debug_attempt counter debug_attempt += 1 - + attempts_left -= 1 + # Output of the module is a concatenated text of the codebase codebase = h.extract_codebase('generated_code') return codebase def modify_codebase(codebase): + module_name = "modify_codebase" while True: # Ask the user if they want to modify the codebase or provide feedback print("\033[92mDo you want to modify the codebase or provide feedback? y/n:\033[00m") @@ -155,9 +156,7 @@ def modify_codebase(codebase): # Add instructions to codebase codebase = codebase + "\n -- User Instructions --" + instructions - module_name = "modify_codebase" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') print("\033[93mModifying codebase...\033[00m") response, messages = ai.generate_response(codebase) logger.log_action(module_name, codebase, response, 'gpt-4') @@ -177,8 +176,7 @@ def modify_codebase(codebase): def create_readme(codebase): module_name = "create_readme" - system_prompt = h.load_system_prompt(module_name) - ai = AI(system=system_prompt, model='gpt-4') + ai = AI(module_name, model='gpt-4') print("\033[93mGenerating README.md...\033[00m") response, messages = ai.generate_response(codebase) diff --git a/orchestrate.py b/orchestrate.py index 56020f8..06bbde9 100644 --- a/orchestrate.py +++ b/orchestrate.py @@ -2,9 +2,22 @@ import networkx as nx import modules import sys +import matplotlib.pyplot as plt +import yaml +import helpers as h +import wandb +# Load the configuration +with open('config.yml', 'r') as f: + config = yaml.safe_load(f) -def execute_pipeline(pipeline): +# Set the wandb_enabled flag +wandb_enabled = config['wandb_enabled'] + + +import matplotlib.patches as mpatches +def execute_pipeline(pipeline): + # Create the DAG G = nx.DiGraph() @@ -21,16 +34,20 @@ def execute_pipeline(pipeline): supplement = operation.get('supplement', '') # Add node for this operation's output if it doesn't already exist - G.add_node(output_name) + G.add_node(output_name, module=module_name) # Add edges for inputs for i in inputs: - G.add_edge(i, output_name) + G.add_edge(i, output_name, label=i) # Add this operation to our ASCII pipeline representation ascii_pipeline += "{} --> {} --> {}\n".format(inputs, module_name, output_name) print(ascii_pipeline) + h.visualize_pipeline(nx, G) + # If wandb is enabled, log the pipeline + if wandb_enabled: + wandb.log({"pipeline": wandb.Image("pipeline.png")}) # Now we use topological sort to get the execution order: try: @@ -63,5 +80,6 @@ def execute_pipeline(pipeline): module_input += '\n Additional User Input' + supplement module_output = module_func(module_input) data_dict[output_name] = module_output + else: - print(f"Warning: No module function '{module_name}'. Ignoring.") + print(f"Warning: No module function '{module_name}'. Ignoring.") \ No newline at end of file diff --git a/pipeline.png b/pipeline.png new file mode 100644 index 0000000..6b2d1c6 Binary files /dev/null and b/pipeline.png differ diff --git a/pipeline.yml b/pipelines/engineering_pipeline.yml similarity index 100% rename from pipeline.yml rename to pipelines/engineering_pipeline.yml diff --git a/plan.txt b/plan.txt new file mode 100644 index 0000000..5c1c637 --- /dev/null +++ b/plan.txt @@ -0,0 +1,24 @@ +#### Here are the remaining things I need to do to finalise the project: + +CODE + +- Add the Weights and Biases logging and or prompts logging to track token usage. DONE +- Fix the debugging so human input actually works. +- Generate pipeline visualisation which I can log to wandb. DONE??? +- Tracking token usage???? - just write about it +- Example pipelines +- README reflective of the project, add demo video(s) + +-- add create pull request module with repositroy. + +REPORT + +- Add a cool gif at the beginning +- Discuss cost. +- Finish the article. +--- talk about the fact the modular system means easy to construct and maintain. +--- decomposable parts means less memory problems. +--- pipeline means you can start from an exisiting repository. +--- self building options. +--- write the Future section. +--- Add references to article diff --git a/prompt.py b/prompt.py deleted file mode 100644 index dccf9f7..0000000 --- a/prompt.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import json - -# Ignore list, a list of files to ignore if name matches -ignore = [ - 'prompt.py', - 'roadmap.md', - 'README.md', -] - -def get_current_dir(): - return os.getcwd() - -def write_content(outfile, filepath, content): - # Writing file path to the output file - outfile.write(f"\n--- File Path: {filepath} ---\n") - - # Writing the cleaned contents of the file to the output file - content_without_spaces = content.replace(' ', '') - outfile.write(content_without_spaces) - outfile.write("\n") - - -def process_file(filename, outfile): - # Check if the file is in the ignore list - if filename in ignore: - return - - filepath = os.path.join(get_current_dir(), filename) - - # Check if the file is a .py or .yml file - if filename.endswith('.yml') or filename.endswith('.py') or filename.endswith('.md'): - try: - with open(filepath, 'r') as infile: - # Read the contents of the file, remove line breaks and leading spaces - content = infile.read().replace('\n', '').replace('\r', '') - content = ' '.join(content.split()) - write_content(outfile, filepath, content) - except Exception: - pass - -def main(): - # Get the current directory - current_dir = get_current_dir() - - # Open the target file - with open('prompt.txt', 'w') as outfile: - # Loop over all files in the current directory - for filename in os.listdir(current_dir): - process_file(filename, outfile) - - # Add the chosen text string at the end - chosen_text = "\nIf you understand, generate only YES." - outfile.write(chosen_text) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7b708b6..8e987e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ -networkx -numpy -openai -PyYAML \ No newline at end of file +globals==0.3.36 +matplotlib==3.7.1 +networkx==2.8.8 +openai==0.27.8 +pandas==2.0.3 +PyYAML==6.0.1 +wandb==0.15.8