promtd commited on
Commit
4764f40
1 Parent(s): d4645ac

1337 Based Ai

Files changed (7) hide show
  1. README.md +171 -11
  2. maestro-gpt.py +236 -0
  3. maestro-groq.py +235 -0
  4. maestro-lmstudio.py +284 -0
  5. maestro-ollama.py +285 -0
  6. maestro.py +304 -0
  7. requirements.txt +5 -0
README.md CHANGED
@@ -1,11 +1,171 @@
1
- ---
2
- title: AIYogaLive
3
- emoji: 😻
4
- colorFrom: blue
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Maestro - A Framework for Claude Opus, GPT and local LLMs to Orchestrate Subagents
2
+
3
+
4
+ This Python script demonstrates an AI-assisted task breakdown and execution workflow using the Anthropic API. It utilizes two AI models, Opus and Haiku, to break down an objective into sub-tasks, execute each sub-task, and refine the results into a cohesive final output.
5
+
6
+ ## New: Run locally with LMStudio or Ollama
7
+
8
+ ### Lmstudio
9
+
10
+ First download the app here
11
+ https://lmstudio.ai/
12
+
13
+ Then run the local server using your preferred method. I also recommend removing any system prompt for the app (leave your prompt field empty so it can take advantage of the script prompts).
14
+
15
+ Then
16
+ ```bash
17
+ python maestro-lmstudio.py
18
+ ```
19
+
20
+
21
+ ### Ollama
22
+ Mestro now runs locally thanks to the Ollama platform. Experience the power of Llama 3 locally!
23
+
24
+ Before running the script
25
+
26
+ Install Ollama client from here
27
+ https://ollama.com/download
28
+
29
+ then
30
+
31
+ ```bash
32
+ pip install ollama
33
+ ```
34
+ And
35
+
36
+ ```bash
37
+ ollama.pull('llama3:70b')
38
+ ```
39
+ This will depend on the model you want to use it, you only need to do it once or if you want to update the model when a new version it's out.
40
+ In the script I am using both versions but you can customize the model you want to use
41
+
42
+ ollama.pull('llama3:70b')
43
+ ollama.pull('llama3:8b')
44
+
45
+ Then
46
+
47
+ ```bash
48
+ python maestro-ollama.py
49
+ ```
50
+
51
+ ## Highly requested features
52
+ - GROQ SUPPORT
53
+ Experience the power of maestro thanks to Groq super fast api responses.
54
+ ```bash
55
+ pip install groq
56
+ ```
57
+ Then
58
+
59
+ ```bash
60
+ python maestro-groq.py
61
+ ```
62
+
63
+
64
+ - SEARCH 🔍
65
+
66
+ Now, when it's creating a task for its subagent, Claude Opus will perform a search and get the best answer to help the subagent solve that task even better.
67
+
68
+ Make sure you replace your Tavil API for search to work
69
+
70
+ Get one here https://tavily.com/
71
+
72
+ - GPT4 SUPPORT
73
+
74
+ Add support for GPT-4 as an orchestrator in maestro-gpt.py
75
+ Simply
76
+ ```bash
77
+ python maestro-gpt.py
78
+ ```
79
+
80
+ After you complete your installs.
81
+
82
+
83
+ ## Features
84
+
85
+ - Breaks down an objective into manageable sub-tasks using the Opus model
86
+ - Executes each sub-task using the Haiku model
87
+ - Provides the Haiku model with memory of previous sub-tasks for context
88
+ - Refines the sub-task results into a final output using the Opus model
89
+ - Generates a detailed exchange log capturing the entire task breakdown and execution process
90
+ - Saves the exchange log to a Markdown file for easy reference
91
+ - Utilizes an improved prompt for the Opus model to better assess task completion
92
+ - Creates code files and folders when working on code projects.
93
+
94
+ ## Prerequisites
95
+
96
+ To run this script, you need to have the following:
97
+
98
+ - Python installed
99
+ - Anthropic API key
100
+ - Required Python packages: `anthropic` and `rich`
101
+
102
+ ## Installation
103
+
104
+ 1. Clone the repository or download the script file.
105
+ 2. Install the required Python packages by running the following command:
106
+
107
+ ```bash
108
+ pip install -r requirements.txt
109
+ ```
110
+
111
+ 3. Replace the placeholder API key in the script with your actual Anthropic API key:
112
+
113
+ ```python
114
+ client = Anthropic(api_key="YOUR_API_KEY_HERE")
115
+ ```
116
+
117
+ If using search, replace your Tavil API
118
+ ```python
119
+ tavily = TavilyClient(api_key="YOUR API KEY HERE")
120
+ ```
121
+
122
+ ## Usage
123
+
124
+ 1. Open a terminal or command prompt and navigate to the directory containing the script.
125
+ 2. Run the script using the following command:
126
+
127
+ ```bash
128
+ python maestro.py
129
+ ```
130
+
131
+ 3. Enter your objective when prompted:
132
+
133
+ ```bash
134
+ Please enter your objective: Your objective here
135
+ ```
136
+
137
+ The script will start the task breakdown and execution process. It will display the progress and results in the console using formatted panels.
138
+
139
+ Once the process is complete, the script will display the refined final output and save the full exchange log to a Markdown file with a filename based on the objective.
140
+
141
+ ## Code Structure
142
+
143
+ The script consists of the following main functions:
144
+
145
+ - `opus_orchestrator(objective, previous_results=None)`: Calls the Opus model to break down the objective into sub-tasks or provide the final output. It uses an improved prompt to assess task completion and includes the phrase "The task is complete:" when the objective is fully achieved.
146
+ - `haiku_sub_agent(prompt, previous_haiku_tasks=None)`: Calls the Haiku model to execute a sub-task prompt, providing it with the memory of previous sub-tasks.
147
+ - `opus_refine(objective, sub_task_results)`: Calls the Opus model to review and refine the sub-task results into a cohesive final output.
148
+
149
+ The script follows an iterative process, repeatedly calling the opus_orchestrator function to break down the objective into sub-tasks until the final output is provided. Each sub-task is then executed by the haiku_sub_agent function, and the results are stored in the task_exchanges and haiku_tasks lists.
150
+
151
+ The loop terminates when the Opus model includes the phrase "The task is complete:" in its response, indicating that the objective has been fully achieved.
152
+
153
+ Finally, the opus_refine function is called to review and refine the sub-task results into a final output. The entire exchange log, including the objective, task breakdown, and refined final output, is saved to a Markdown file.
154
+
155
+ ## Customization
156
+
157
+ You can customize the script according to your needs:
158
+
159
+ - Adjust the max_tokens parameter in the client.messages.create() function calls to control the maximum number of tokens generated by the AI models.
160
+ - Change the models to what you prefer, like replacing Haiku with Sonnet or Opus.
161
+ - Modify the console output formatting by updating the rich library's Panel and Console configurations.
162
+ - Customize the exchange log formatting and file extension by modifying the relevant code sections.
163
+
164
+ ## License
165
+
166
+ This script is released under the MIT License.
167
+
168
+ ## Acknowledgements
169
+
170
+ - Anthropic for providing the AI models and API.
171
+ - Rich for the beautiful console formatting.
maestro-gpt.py ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from anthropic import Anthropic
3
+ from openai import OpenAI
4
+ import re
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ from datetime import datetime
8
+ import json
9
+
10
+ # Set up the Anthropic API client
11
+ anthropic_client = Anthropic(api_key="YOUR ANTHROPIC API KEY")
12
+
13
+ # Set up the OpenAI API client
14
+ openai_client = OpenAI(api_key="YOUR OPENAI API KEY")
15
+
16
+ # Set the Claude model to use for the sub-agent
17
+ claude_model = "claude-3-opus-20240229"
18
+
19
+ # Initialize the Rich Console
20
+ console = Console()
21
+
22
+ def opus_orchestrator(objective, file_content=None, previous_results=None):
23
+ console.print(f"\n[bold]Calling Orchestrator for your objective[/bold]")
24
+ previous_results_text = "\n".join(previous_results) if previous_results else "None"
25
+ if file_content:
26
+ console.print(Panel(f"File content:\n{file_content}", title="[bold blue]File Content[/bold blue]", title_align="left", border_style="blue"))
27
+ messages = [
28
+ {
29
+ "role": "user",
30
+ "content": [
31
+ {"type": "text", "text": f"Based on the following objective{' and file content' if file_content else ''}, and the previous sub-task results (if any), please break down the objective into the next sub-task, and create a concise and detailed prompt for a subagent so it can execute that task. IMPORTANT!!! when dealing with code tasks make sure you check the code for errors and provide fixes and support as part of the next sub-task. If you find any bugs or have suggestions for better code, please include them in the next sub-task prompt. Please assess if the objective has been fully achieved. If the previous sub-task results comprehensively address all aspects of the objective, include the phrase 'The task is complete:' at the beginning of your response. If the objective is not yet fully achieved, break it down into the next sub-task and create a concise and detailed prompt for a subagent to execute that task.:\n\nObjective: {objective}" + ('\\nFile content:\\n' + file_content if file_content else '') + f"\n\nPrevious sub-task results:\n{previous_results_text}"}
32
+ ]
33
+ }
34
+ ]
35
+
36
+ if orchestrator_model == "Claude Opus":
37
+ opus_response = anthropic_client.messages.create(
38
+ model="claude-3-opus-20240229",
39
+ max_tokens=4096,
40
+ messages=messages
41
+ )
42
+ response_text = opus_response.content[0].text
43
+ else: # GPT-4
44
+ gpt4_response = openai_client.chat.completions.create(
45
+ model="gpt-4-0125-preview",
46
+ messages=messages
47
+ )
48
+ response_text = gpt4_response.choices[0].message.content
49
+
50
+ console.print(Panel(response_text, title=f"[bold green]{orchestrator_model} Orchestrator[/bold green]", title_align="left", border_style="green", subtitle="Sending task to subagent 👇"))
51
+ return response_text, file_content
52
+
53
+ def subagent(prompt, previous_subagent_tasks=None):
54
+ if previous_subagent_tasks is None:
55
+ previous_subagent_tasks = []
56
+
57
+ system_message = "Previous subagent tasks:\n" + "\n".join(previous_subagent_tasks)
58
+
59
+ messages = [
60
+ {
61
+ "role": "user",
62
+ "content": [
63
+ {"type": "text", "text": prompt}
64
+ ]
65
+ }
66
+ ]
67
+
68
+ subagent_response = anthropic_client.messages.create(
69
+ model=claude_model,
70
+ max_tokens=4096,
71
+ messages=messages,
72
+ system=system_message
73
+ )
74
+
75
+ response_text = subagent_response.content[0].text
76
+ console.print(Panel(response_text, title="[bold blue]Subagent Result[/bold blue]", title_align="left", border_style="blue", subtitle="Task completed, sending result to Maestro 👇"))
77
+ return response_text
78
+
79
+ def opus_refine(objective, sub_task_results, filename, projectname):
80
+ print("\nCalling Opus to provide the refined final output for your objective:")
81
+ messages = [
82
+ {
83
+ "role": "user",
84
+ "content": [
85
+ {"type": "text", "text": "Objective: " + objective + "\n\nSub-task results:\n" + "\n".join(sub_task_results) + "\n\nPlease review and refine the sub-task results into a cohesive final output. Add any missing information or details as needed. When working on code projects, ONLY AND ONLY IF THE PROJECT IS CLEARLY A CODING ONE please provide the following:\n1. Project Name: Create a concise and appropriate project name that fits the project based on what it's creating. The project name should be no more than 20 characters long.\n2. Folder Structure: Provide the folder structure as a valid JSON object, where each key represents a folder or file, and nested keys represent subfolders. Use null values for files. Ensure the JSON is properly formatted without any syntax errors. Please make sure all keys are enclosed in double quotes, and ensure objects are correctly encapsulated with braces, separating items with commas as necessary.\nWrap the JSON object in <folder_structure> tags.\n3. Code Files: For each code file, include ONLY the file name NEVER EVER USE THE FILE PATH OR ANY OTHER FORMATTING YOU ONLY USE THE FOLLOWING format 'Filename: <filename>' followed by the code block enclosed in triple backticks, with the language identifier after the opening backticks, like this:\n\n​python\n<code>\n​"}
86
+ ]
87
+ }
88
+ ]
89
+
90
+ opus_response = anthropic_client.messages.create(
91
+ model="claude-3-opus-20240229",
92
+ max_tokens=4096,
93
+ messages=messages
94
+ )
95
+
96
+ response_text = opus_response.content[0].text
97
+ console.print(Panel(response_text, title="[bold green]Final Output[/bold green]", title_align="left", border_style="green"))
98
+ return response_text
99
+
100
+ def create_folder_structure(project_name, folder_structure, code_blocks):
101
+ # Create the project folder
102
+ try:
103
+ os.makedirs(project_name, exist_ok=True)
104
+ console.print(Panel(f"Created project folder: [bold]{project_name}[/bold]", title="[bold green]Project Folder[/bold green]", title_align="left", border_style="green"))
105
+ except OSError as e:
106
+ console.print(Panel(f"Error creating project folder: [bold]{project_name}[/bold]\nError: {e}", title="[bold red]Project Folder Creation Error[/bold red]", title_align="left", border_style="red"))
107
+ return
108
+
109
+ # Recursively create the folder structure and files
110
+ create_folders_and_files(project_name, folder_structure, code_blocks)
111
+
112
+ def create_folders_and_files(current_path, structure, code_blocks):
113
+ for key, value in structure.items():
114
+ path = os.path.join(current_path, key)
115
+ if isinstance(value, dict):
116
+ try:
117
+ os.makedirs(path, exist_ok=True)
118
+ console.print(Panel(f"Created folder: [bold]{path}[/bold]", title="[bold blue]Folder Creation[/bold blue]", title_align="left", border_style="blue"))
119
+ create_folders_and_files(path, value, code_blocks)
120
+ except OSError as e:
121
+ console.print(Panel(f"Error creating folder: [bold]{path}[/bold]\nError: {e}", title="[bold red]Folder Creation Error[/bold red]", title_align="left", border_style="red"))
122
+ else:
123
+ code_content = next((code for file, code in code_blocks if file == key), None)
124
+ if code_content:
125
+ try:
126
+ with open(path, 'w') as file:
127
+ file.write(code_content)
128
+ console.print(Panel(f"Created file: [bold]{path}[/bold]", title="[bold green]File Creation[/bold green]", title_align="left", border_style="green"))
129
+ except IOError as e:
130
+ console.print(Panel(f"Error creating file: [bold]{path}[/bold]\nError: {e}", title="[bold red]File Creation Error[/bold red]", title_align="left", border_style="red"))
131
+ else:
132
+ console.print(Panel(f"Code content not found for file: [bold]{key}[/bold]", title="[bold yellow]Missing Code Content[/bold yellow]", title_align="left", border_style="yellow"))
133
+
134
+ def read_file(file_path):
135
+ with open(file_path, 'r') as file:
136
+ content = file.read()
137
+ return content
138
+
139
+ # Ask the user for the orchestrator model choice
140
+ orchestrator_model = input("Please choose the orchestrator model (Claude Opus or GPT-4): ")
141
+ while orchestrator_model not in ["Claude Opus", "GPT-4"]:
142
+ orchestrator_model = input("Invalid choice. Please enter 'Claude Opus' or 'GPT-4': ")
143
+
144
+ # Get the objective from user input
145
+ objective = input("Please enter your objective with or without a text file path: ")
146
+
147
+ # Check if the input contains a file path
148
+ if "./" in objective or "/" in objective:
149
+ # Extract the file path from the objective
150
+ file_path = re.findall(r'[./\w]+\.[\w]+', objective)[0]
151
+ # Read the file content
152
+ with open(file_path, 'r') as file:
153
+ file_content = file.read()
154
+ # Update the objective string to remove the file path
155
+ objective = objective.split(file_path)[0].strip()
156
+ else:
157
+ file_content = None
158
+
159
+ task_exchanges = []
160
+ subagent_tasks = []
161
+
162
+ while True:
163
+ # Call Orchestrator to break down the objective into the next sub-task or provide the final output
164
+ previous_results = [result for _, result in task_exchanges]
165
+ if not task_exchanges:
166
+ # Pass the file content only in the first iteration if available
167
+ opus_result, file_content_for_subagent = opus_orchestrator(objective, file_content, previous_results)
168
+ else:
169
+ opus_result, _ = opus_orchestrator(objective, previous_results=previous_results)
170
+
171
+ if "The task is complete:" in opus_result:
172
+ # If Opus indicates the task is complete, exit the loop
173
+ final_output = opus_result.replace("The task is complete:", "").strip()
174
+ break
175
+ else:
176
+ sub_task_prompt = opus_result
177
+ # Include file content in the first subagent call if available
178
+ if file_content_for_subagent and not subagent_tasks:
179
+ sub_task_prompt += "\n\nFile content:\n" + file_content_for_subagent
180
+ sub_task_result = subagent(sub_task_prompt, subagent_tasks)
181
+ subagent_tasks.append(f"Task: {sub_task_prompt}\nResult: {sub_task_result}")
182
+ task_exchanges.append((sub_task_prompt, sub_task_result))
183
+ # Ensure file content is not passed in subsequent calls
184
+ file_content_for_subagent = None
185
+
186
+ # Create the .md filename
187
+ sanitized_objective = re.sub(r'\W+', '_', objective)
188
+ timestamp = datetime.now().strftime("%H-%M-%S")
189
+
190
+ # Call Opus to review and refine the sub-task results
191
+ refined_output = opus_refine(objective, [result for _, result in task_exchanges], timestamp, sanitized_objective)
192
+
193
+ # Extract the project name from the refined output
194
+ project_name_match = re.search(r'Project Name: (.*)', refined_output)
195
+ project_name = project_name_match.group(1).strip() if project_name_match else sanitized_objective
196
+
197
+ # Extract the folder structure from the refined output
198
+ folder_structure_match = re.search(r'<folder_structure>(.*?)</folder_structure>', refined_output, re.DOTALL)
199
+ folder_structure = {}
200
+ if folder_structure_match:
201
+ json_string = folder_structure_match.group(1).strip()
202
+ try:
203
+ folder_structure = json.loads(json_string)
204
+ except json.JSONDecodeError as e:
205
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
206
+ console.print(Panel(f"Invalid JSON string: [bold]{json_string}[/bold]", title="[bold red]Invalid JSON String[/bold red]", title_align="left", border_style="red"))
207
+
208
+ # Extract code files from the refined output
209
+ code_blocks = re.findall(r'Filename: (\S+)\s*```[\w]*\n(.*?)\n```', refined_output, re.DOTALL)
210
+
211
+ # Create the folder structure and code files
212
+ create_folder_structure(project_name, folder_structure, code_blocks)
213
+
214
+ # Truncate the sanitized_objective to a maximum of 50 characters
215
+ max_length = 40
216
+ truncated_objective = sanitized_objective[:max_length] if len(sanitized_objective) > max_length else sanitized_objective
217
+
218
+ # Update the filename to include the project name
219
+ filename = f"{timestamp}_{truncated_objective}.md"
220
+
221
+ # Prepare the full exchange log
222
+ exchange_log = f"Objective: {objective}\n\n"
223
+ exchange_log += "=" * 40 + " Task Breakdown " + "=" * 40 + "\n\n"
224
+ for i, (prompt, result) in enumerate(task_exchanges, start=1):
225
+ exchange_log += f"Task {i}:\n"
226
+ exchange_log += f"Prompt: {prompt}\n"
227
+ exchange_log += f"Result: {result}\n\n"
228
+
229
+ exchange_log += "=" * 40 + " Refined Final Output " + "=" * 40 + "\n\n"
230
+ exchange_log += refined_output
231
+
232
+ console.print(f"\n[bold]Refined Final output:[/bold]\n{refined_output}")
233
+
234
+ with open(filename, 'w') as file:
235
+ file.write(exchange_log)
236
+ print(f"\nFull exchange log saved to {filename}")
maestro-groq.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from rich.console import Console
4
+ from rich.panel import Panel
5
+ from datetime import datetime
6
+ import json
7
+
8
+ # Set up the Groq API client
9
+ from groq import Groq
10
+ import os
11
+
12
+ client = Groq(api_key="YOUR API KEY")
13
+
14
+ # Define the models to use for each agent
15
+ ORCHESTRATOR_MODEL = "mixtral-8x7b-32768"
16
+ SUB_AGENT_MODEL = "mixtral-8x7b-32768"
17
+ REFINER_MODEL = "llama3-70b-8192"
18
+
19
+ # Initialize the Rich Console
20
+ console = Console()
21
+
22
+ def opus_orchestrator(objective, file_content=None, previous_results=None):
23
+ console.print(f"\n[bold]Calling Orchestrator for your objective[/bold]")
24
+ previous_results_text = "\n".join(previous_results) if previous_results else "None"
25
+ if file_content:
26
+ console.print(Panel(f"File content:\n{file_content}", title="[bold blue]File Content[/bold blue]", title_align="left", border_style="blue"))
27
+ messages = [
28
+ {
29
+ "role": "system",
30
+ "content": "You are an AI orchestrator that breaks down objectives into sub-tasks."
31
+ },
32
+ {
33
+ "role": "user",
34
+ "content": f"Based on the following objective{' and file content' if file_content else ''}, and the previous sub-task results (if any), please break down the objective into the next sub-task, and create a concise and detailed prompt for a subagent so it can execute that task. IMPORTANT!!! when dealing with code tasks make sure you check the code for errors and provide fixes and support as part of the next sub-task. If you find any bugs or have suggestions for better code, please include them in the next sub-task prompt. Please assess if the objective has been fully achieved. If the previous sub-task results comprehensively address all aspects of the objective, include the phrase 'The task is complete:' at the beginning of your response. If the objective is not yet fully achieved, break it down into the next sub-task and create a concise and detailed prompt for a subagent to execute that task.:\n\nObjective: {objective}" + ('\\nFile content:\\n' + file_content if file_content else '') + f"\n\nPrevious sub-task results:\n{previous_results_text}"
35
+ }
36
+ ]
37
+
38
+ opus_response = client.chat.completions.create(
39
+ model=ORCHESTRATOR_MODEL,
40
+ messages=messages,
41
+ max_tokens=8000
42
+ )
43
+
44
+ response_text = opus_response.choices[0].message.content
45
+ console.print(Panel(response_text, title=f"[bold green]Groq Orchestrator[/bold green]", title_align="left", border_style="green", subtitle="Sending task to Subagent 👇"))
46
+ return response_text, file_content
47
+
48
+ def haiku_sub_agent(prompt, previous_haiku_tasks=None, continuation=False):
49
+ if previous_haiku_tasks is None:
50
+ previous_haiku_tasks = []
51
+
52
+ continuation_prompt = "Continuing from the previous answer, please complete the response."
53
+ system_message = "Previous Haiku tasks:\n" + "\n".join(f"Task: {task['task']}\nResult: {task['result']}" for task in previous_haiku_tasks)
54
+ if continuation:
55
+ prompt = continuation_prompt
56
+
57
+ messages = [
58
+ {
59
+ "role": "system",
60
+ "content": system_message
61
+ },
62
+ {
63
+ "role": "user",
64
+ "content": prompt
65
+ }
66
+ ]
67
+
68
+ haiku_response = client.chat.completions.create(
69
+ model=SUB_AGENT_MODEL,
70
+ messages=messages,
71
+ max_tokens=8000
72
+ )
73
+
74
+ response_text = haiku_response.choices[0].message.content
75
+ console.print(Panel(response_text, title="[bold blue]Groq Sub-agent Result[/bold blue]", title_align="left", border_style="blue", subtitle="Task completed, sending result to Orchestrator 👇"))
76
+ return response_text
77
+
78
+ def opus_refine(objective, sub_task_results, filename, projectname, continuation=False):
79
+ console.print("\nCalling Opus to provide the refined final output for your objective:")
80
+ messages = [
81
+ {
82
+ "role": "system",
83
+ "content": "You are an AI assistant that refines sub-task results into a cohesive final output."
84
+ },
85
+ {
86
+ "role": "user",
87
+ "content": "Objective: " + objective + "\n\nSub-task results:\n" + "\n".join(sub_task_results) + "\n\nPlease review and refine the sub-task results into a cohesive final output. Add any missing information or details as needed. Make sure the code files are completed. When working on code projects, ONLY AND ONLY IF THE PROJECT IS CLEARLY A CODING ONE please provide the following:\n1. Project Name: Create a concise and appropriate project name that fits the project based on what it's creating. The project name should be no more than 20 characters long.\n2. Folder Structure: Provide the folder structure as a valid JSON object, where each key represents a folder or file, and nested keys represent subfolders. Use null values for files. Ensure the JSON is properly formatted without any syntax errors. Please make sure all keys are enclosed in double quotes, and ensure objects are correctly encapsulated with braces, separating items with commas as necessary.\nWrap the JSON object in <folder_structure> tags.\n3. Code Files: For each code file, include ONLY the file name in this format 'Filename: <filename>' NEVER EVER USE THE FILE PATH OR ANY OTHER FORMATTING YOU ONLY USE THE FOLLOWING format 'Filename: <filename>' followed by the code block enclosed in triple backticks, with the language identifier after the opening backticks, like this:\n\n​python\n<code>\n​"
88
+ }
89
+ ]
90
+
91
+ opus_response = client.chat.completions.create(
92
+ model=REFINER_MODEL,
93
+ messages=messages,
94
+ max_tokens=8000
95
+ )
96
+
97
+ response_text = opus_response.choices[0].message.content
98
+ console.print(Panel(response_text, title="[bold green]Final Output[/bold green]", title_align="left", border_style="green"))
99
+ return response_text
100
+
101
+ def create_folder_structure(project_name, folder_structure, code_blocks):
102
+ # Create the project folder
103
+ try:
104
+ os.makedirs(project_name, exist_ok=True)
105
+ console.print(Panel(f"Created project folder: [bold]{project_name}[/bold]", title="[bold green]Project Folder[/bold green]", title_align="left", border_style="green"))
106
+ except OSError as e:
107
+ console.print(Panel(f"Error creating project folder: [bold]{project_name}[/bold]\nError: {e}", title="[bold red]Project Folder Creation Error[/bold red]", title_align="left", border_style="red"))
108
+ return
109
+
110
+ # Recursively create the folder structure and files
111
+ create_folders_and_files(project_name, folder_structure, code_blocks)
112
+
113
+ def create_folders_and_files(current_path, structure, code_blocks):
114
+ for key, value in structure.items():
115
+ path = os.path.join(current_path, key)
116
+ if isinstance(value, dict):
117
+ try:
118
+ os.makedirs(path, exist_ok=True)
119
+ console.print(Panel(f"Created folder: [bold]{path}[/bold]", title="[bold blue]Folder Creation[/bold blue]", title_align="left", border_style="blue"))
120
+ create_folders_and_files(path, value, code_blocks)
121
+ except OSError as e:
122
+ console.print(Panel(f"Error creating folder: [bold]{path}[/bold]\nError: {e}", title="[bold red]Folder Creation Error[/bold red]", title_align="left", border_style="red"))
123
+ else:
124
+ code_content = next((code for file, code in code_blocks if file == key), None)
125
+ if code_content:
126
+ try:
127
+ with open(path, 'w') as file:
128
+ file.write(code_content)
129
+ console.print(Panel(f"Created file: [bold]{path}[/bold]", title="[bold green]File Creation[/bold green]", title_align="left", border_style="green"))
130
+ except IOError as e:
131
+ console.print(Panel(f"Error creating file: [bold]{path}[/bold]\nError: {e}", title="[bold red]File Creation Error[/bold red]", title_align="left", border_style="red"))
132
+ else:
133
+ console.print(Panel(f"Code content not found for file: [bold]{key}[/bold]", title="[bold yellow]Missing Code Content[/bold yellow]", title_align="left", border_style="yellow"))
134
+
135
+ def read_file(file_path):
136
+ with open(file_path, 'r') as file:
137
+ content = file.read()
138
+ return content
139
+
140
+ # Get the objective from user input
141
+ objective = input("Please enter your objective with or without a text file path: ")
142
+
143
+ # Check if the input contains a file path
144
+ if "./" in objective or "/" in objective:
145
+ # Extract the file path from the objective
146
+ file_path = re.findall(r'[./\w]+\.[\w]+', objective)[0]
147
+ # Read the file content
148
+ with open(file_path, 'r') as file:
149
+ file_content = file.read()
150
+ # Update the objective string to remove the file path
151
+ objective = objective.split(file_path)[0].strip()
152
+ else:
153
+ file_content = None
154
+
155
+ task_exchanges = []
156
+ haiku_tasks = []
157
+
158
+ while True:
159
+ # Call Orchestrator to break down the objective into the next sub-task or provide the final output
160
+ previous_results = [result for _, result in task_exchanges]
161
+ if not task_exchanges:
162
+ # Pass the file content only in the first iteration if available
163
+ opus_result, file_content_for_haiku = opus_orchestrator(objective, file_content, previous_results)
164
+ else:
165
+ opus_result, _ = opus_orchestrator(objective, previous_results=previous_results)
166
+
167
+ if "The task is complete:" in opus_result:
168
+ # If Opus indicates the task is complete, exit the loop
169
+ final_output = opus_result.replace("The task is complete:", "").strip()
170
+ break
171
+ else:
172
+ sub_task_prompt = opus_result
173
+ # Append file content to the prompt for the initial call to haiku_sub_agent, if applicable
174
+ if file_content_for_haiku and not haiku_tasks:
175
+ sub_task_prompt = f"{sub_task_prompt}\n\nFile content:\n{file_content_for_haiku}"
176
+ # Call haiku_sub_agent with the prepared prompt and record the result
177
+ sub_task_result = haiku_sub_agent(sub_task_prompt, haiku_tasks)
178
+ # Log the task and its result for future reference
179
+ haiku_tasks.append({"task": sub_task_prompt, "result": sub_task_result})
180
+ # Record the exchange for processing and output generation
181
+ task_exchanges.append((sub_task_prompt, sub_task_result))
182
+ # Prevent file content from being included in future haiku_sub_agent calls
183
+ file_content_for_haiku = None
184
+
185
+ # Create the .md filename
186
+ sanitized_objective = re.sub(r'\W+', '_', objective)
187
+ timestamp = datetime.now().strftime("%H-%M-%S")
188
+
189
+ # Call Opus to review and refine the sub-task results
190
+ refined_output = opus_refine(objective, [result for _, result in task_exchanges], timestamp, sanitized_objective)
191
+
192
+ # Extract the project name from the refined output
193
+ project_name_match = re.search(r'Project Name: (.*)', refined_output)
194
+ project_name = project_name_match.group(1).strip() if project_name_match else sanitized_objective
195
+
196
+ # Extract the folder structure from the refined output
197
+ folder_structure_match = re.search(r'<folder_structure>(.*?)</folder_structure>', refined_output, re.DOTALL)
198
+ folder_structure = {}
199
+ if folder_structure_match:
200
+ json_string = folder_structure_match.group(1).strip()
201
+ try:
202
+ folder_structure = json.loads(json_string)
203
+ except json.JSONDecodeError as e:
204
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
205
+ console.print(Panel(f"Invalid JSON string: [bold]{json_string}[/bold]", title="[bold red]Invalid JSON String[/bold red]", title_align="left", border_style="red"))
206
+
207
+ # Extract code files from the refined output
208
+ code_blocks = re.findall(r'Filename: (\S+)\s*```[\w]*\n(.*?)\n```', refined_output, re.DOTALL)
209
+
210
+ # Create the folder structure and code files
211
+ create_folder_structure(project_name, folder_structure, code_blocks)
212
+
213
+ # Truncate the sanitized_objective to a maximum of 50 characters
214
+ max_length = 25
215
+ truncated_objective = sanitized_objective[:max_length] if len(sanitized_objective) > max_length else sanitized_objective
216
+
217
+ # Update the filename to include the project name
218
+ filename = f"{timestamp}_{truncated_objective}.md"
219
+
220
+ # Prepare the full exchange log
221
+ exchange_log = f"Objective: {objective}\n\n"
222
+ exchange_log += "=" * 40 + " Task Breakdown " + "=" * 40 + "\n\n"
223
+ for i, (prompt, result) in enumerate(task_exchanges, start=1):
224
+ exchange_log += f"Task {i}:\n"
225
+ exchange_log += f"Prompt: {prompt}\n"
226
+ exchange_log += f"Result: {result}\n\n"
227
+
228
+ exchange_log += "=" * 40 + " Refined Final Output " + "=" * 40 + "\n\n"
229
+ exchange_log += refined_output
230
+
231
+ console.print(f"\n[bold]Refined Final output:[/bold]\n{refined_output}")
232
+
233
+ with open(filename, 'w') as file:
234
+ file.write(exchange_log)
235
+ print(f"\nFull exchange log saved to {filename}")
maestro-lmstudio.py ADDED
@@ -0,0 +1,284 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from rich.console import Console
4
+ from rich.panel import Panel
5
+ from datetime import datetime
6
+ import json
7
+ from tavily import TavilyClient
8
+ from openai import OpenAI
9
+
10
+ # Set up the LM Studio API client
11
+ client = OpenAI(base_url="http://localhost:1234/v1", api_key="lm-studio")
12
+
13
+ # Available models (replace with your own model names)
14
+ ORCHESTRATOR_MODEL = "lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF"
15
+ SUB_AGENT_MODEL = "lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF"
16
+ REFINER_MODEL = "lmstudio-community/Meta-Llama-3-8B-Instruct-GGUF"
17
+
18
+ # Initialize the Rich Console
19
+ console = Console()
20
+
21
+ def opus_orchestrator(objective, file_content=None, previous_results=None, use_search=False):
22
+ console.print(f"\n[bold]Calling Orchestrator for your objective[/bold]")
23
+ previous_results_text = "\n".join(previous_results) if previous_results else "None"
24
+ if file_content:
25
+ console.print(Panel(f"File content:\n{file_content}", title="[bold blue]File Content[/bold blue]", title_align="left", border_style="blue"))
26
+
27
+ messages = [
28
+ {
29
+ "role": "system",
30
+ "content": "You are an orchestrator responsible for breaking down complex tasks into smaller sub-tasks."
31
+ },
32
+ {
33
+ "role": "user",
34
+ "content": f"Based on the following objective{' and file content' if file_content else ''}, and the previous sub-task results (if any), please break down the objective into the next sub-task, and create a concise and detailed prompt for a subagent so it can execute that task. IMPORTANT!!! when dealing with code tasks make sure you check the code for errors and provide fixes and support as part of the next sub-task. If you find any bugs or have suggestions for better code, please include them in the next sub-task prompt. Please assess if the objective has been fully achieved. If the previous sub-task results comprehensively address all aspects of the objective, include the phrase 'The task is complete:' at the beginning of your response. If the objective is not yet fully achieved, break it down into the next sub-task and create a concise and detailed prompt for a subagent to execute that task.:\n\nObjective: {objective}" + ('\\nFile content:\\n' + file_content if file_content else '') + f"\n\nPrevious sub-task results:\n{previous_results_text}"
35
+ }
36
+ ]
37
+ if use_search:
38
+ messages.append({
39
+ "role": "user",
40
+ "content": "Please also generate a JSON object containing a single 'search_query' key, which represents a question that, when asked online, would yield important information for solving the subtask. The question should be specific and targeted to elicit the most relevant and helpful resources. Format your JSON like this, with no additional text before or after:\n{\"search_query\": \"<question>\"}\n"
41
+ })
42
+
43
+ opus_response = client.chat.completions.create(
44
+ model=ORCHESTRATOR_MODEL,
45
+ messages=messages,
46
+ temperature=0.7,
47
+ )
48
+
49
+ response_text = opus_response.choices[0].message.content
50
+
51
+ search_query = None
52
+ if use_search:
53
+ # Extract the JSON from the response
54
+ json_match = re.search(r'{.*}', response_text, re.DOTALL)
55
+ if json_match:
56
+ json_string = json_match.group()
57
+ try:
58
+ search_query = json.loads(json_string)["search_query"]
59
+ console.print(Panel(f"Search Query: {search_query}", title="[bold blue]Search Query[/bold blue]", title_align="left", border_style="blue"))
60
+ response_text = response_text.replace(json_string, "").strip()
61
+ except json.JSONDecodeError as e:
62
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
63
+ console.print(Panel(f"Skipping search query extraction.", title="[bold yellow]Search Query Extraction Skipped[/bold yellow]", title_align="left", border_style="yellow"))
64
+ else:
65
+ search_query = None
66
+
67
+ console.print(Panel(response_text, title=f"[bold green]Orchestrator[/bold green]", title_align="left", border_style="green", subtitle="Sending task to Haiku 👇"))
68
+ return response_text, file_content, search_query
69
+
70
+
71
+ def haiku_sub_agent(prompt, search_query=None, previous_haiku_tasks=None, use_search=False, continuation=False):
72
+ if previous_haiku_tasks is None:
73
+ previous_haiku_tasks = []
74
+
75
+ continuation_prompt = "Continuing from the previous answer, please complete the response."
76
+ system_message = "Previous Haiku tasks:\n" + "\n".join(f"Task: {task['task']}\nResult: {task['result']}" for task in previous_haiku_tasks)
77
+ if continuation:
78
+ prompt = continuation_prompt
79
+
80
+ qna_response = None
81
+ if search_query and use_search:
82
+ # Initialize the Tavily client
83
+ tavily = TavilyClient(api_key="YOUR API KEY HERE")
84
+ # Perform a QnA search based on the search query
85
+ qna_response = tavily.qna_search(query=search_query)
86
+ console.print(f"QnA response: {qna_response}", style="yellow")
87
+
88
+ # Prepare the messages array with only the prompt initially
89
+ messages = [
90
+ {
91
+ "role": "system",
92
+ "content": system_message
93
+ },
94
+ {
95
+ "role": "user",
96
+ "content": prompt
97
+ }
98
+ ]
99
+
100
+ # Add search results to the messages if there are any
101
+ if qna_response:
102
+ messages.append({
103
+ "role": "user",
104
+ "content": f"\nSearch Results:\n{qna_response}"
105
+ })
106
+
107
+ haiku_response = client.chat.completions.create(
108
+ model=SUB_AGENT_MODEL,
109
+ messages=messages,
110
+ temperature=0.7,
111
+ )
112
+
113
+ response_text = haiku_response.choices[0].message.content
114
+
115
+ console.print(Panel(response_text, title="[bold blue]Sub-agent Result[/bold blue]", title_align="left", border_style="blue", subtitle="Task completed, sending result to Opus 👇"))
116
+ return response_text
117
+
118
+ def opus_refine(objective, sub_task_results, filename, projectname, continuation=False):
119
+ print("\nCalling Opus to provide the refined final output for your objective:")
120
+ messages = [
121
+ {
122
+ "role": "system",
123
+ "content": "You are responsible for refining the sub-task results into a cohesive final output."
124
+ },
125
+ {
126
+ "role": "user",
127
+ "content": "Objective: " + objective + "\n\nSub-task results:\n" + "\n".join(sub_task_results) + "\n\nPlease review and refine the sub-task results into a cohesive final output. Add any missing information or details as needed. When working on code projects, ONLY AND ONLY IF THE PROJECT IS CLEARLY A CODING ONE please provide the following:\n1. Project Name: Create a concise and appropriate project name that fits the project based on what it's creating. The project name should be no more than 20 characters long.\n2. Folder Structure: Provide the folder structure as a valid JSON object, where each key represents a folder or file, and nested keys represent subfolders. Use null values for files. Ensure the JSON is properly formatted without any syntax errors. Please make sure all keys are enclosed in double quotes, and ensure objects are correctly encapsulated with braces, separating items with commas as necessary.\nWrap the JSON object in <folder_structure> tags.\n3. Code Files: For each code file, include ONLY the file name NEVER EVER USE THE FILE PATH OR ANY OTHER FORMATTING YOU ONLY USE THE FOLLOWING format 'Filename: <filename>' followed by the code block enclosed in triple backticks, with the language identifier after the opening backticks, like this:\n\n​python\n<code>\n​"
128
+ }
129
+ ]
130
+
131
+ opus_response = client.chat.completions.create(
132
+ model=REFINER_MODEL,
133
+ messages=messages,
134
+ temperature=0.7,
135
+ )
136
+
137
+ response_text = opus_response.choices[0].message.content.strip()
138
+
139
+ if opus_response.usage.total_tokens >= 4000: # Threshold set to 4000 as a precaution
140
+ console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
141
+ continuation_response_text = opus_refine(objective, sub_task_results, filename, projectname, continuation=True)
142
+ response_text += continuation_response_text
143
+
144
+ console.print(Panel(response_text, title="[bold green]Final Output[/bold green]", title_align="left", border_style="green"))
145
+ return response_text
146
+
147
+ def create_folder_structure(project_name, folder_structure, code_blocks):
148
+ # Create the project folder
149
+ try:
150
+ os.makedirs(project_name, exist_ok=True)
151
+ console.print(Panel(f"Created project folder: [bold]{project_name}[/bold]", title="[bold green]Project Folder[/bold green]", title_align="left", border_style="green"))
152
+ except OSError as e:
153
+ console.print(Panel(f"Error creating project folder: [bold]{project_name}[/bold]\nError: {e}", title="[bold red]Project Folder Creation Error[/bold red]", title_align="left", border_style="red"))
154
+ return
155
+
156
+ # Recursively create the folder structure and files
157
+ create_folders_and_files(project_name, folder_structure, code_blocks)
158
+
159
+ def create_folders_and_files(current_path, structure, code_blocks):
160
+ for key, value in structure.items():
161
+ path = os.path.join(current_path, key)
162
+ if isinstance(value, dict):
163
+ try:
164
+ os.makedirs(path, exist_ok=True)
165
+ console.print(Panel(f"Created folder: [bold]{path}[/bold]", title="[bold blue]Folder Creation[/bold blue]", title_align="left", border_style="blue"))
166
+ create_folders_and_files(path, value, code_blocks)
167
+ except OSError as e:
168
+ console.print(Panel(f"Error creating folder: [bold]{path}[/bold]\nError: {e}", title="[bold red]Folder Creation Error[/bold red]", title_align="left", border_style="red"))
169
+ else:
170
+ code_content = next((code for file, code in code_blocks if file == key), None)
171
+ if code_content:
172
+ try:
173
+ with open(path, 'w') as file:
174
+ file.write(code_content)
175
+ console.print(Panel(f"Created file: [bold]{path}[/bold]", title="[bold green]File Creation[/bold green]", title_align="left", border_style="green"))
176
+ except IOError as e:
177
+ console.print(Panel(f"Error creating file: [bold]{path}[/bold]\nError: {e}", title="[bold red]File Creation Error[/bold red]", title_align="left", border_style="red"))
178
+ else:
179
+ console.print(Panel(f"Code content not found for file: [bold]{key}[/bold]", title="[bold yellow]Missing Code Content[/bold yellow]", title_align="left", border_style="yellow"))
180
+
181
+ def read_file(file_path):
182
+ with open(file_path, 'r') as file:
183
+ content = file.read()
184
+ return content
185
+
186
+ # Get the objective from user input
187
+ objective = input("Please enter your objective with or without a text file path: ")
188
+
189
+ # Check if the input contains a file path
190
+ if "./" in objective or "/" in objective:
191
+ # Extract the file path from the objective
192
+ file_path = re.findall(r'[./\w]+\.[\w]+', objective)[0]
193
+ # Read the file content
194
+ with open(file_path, 'r') as file:
195
+ file_content = file.read()
196
+ # Update the objective string to remove the file path
197
+ objective = objective.split(file_path)[0].strip()
198
+ else:
199
+ file_content = None
200
+
201
+ # Ask the user if they want to use search
202
+ use_search = input("Do you want to use search? (y/n): ").lower() == 'y'
203
+
204
+ task_exchanges = []
205
+ haiku_tasks = []
206
+
207
+ while True:
208
+ # Call Orchestrator to break down the objective into the next sub-task or provide the final output
209
+ previous_results = [result for _, result in task_exchanges]
210
+ if not task_exchanges:
211
+ # Pass the file content only in the first iteration if available
212
+ opus_result, file_content_for_haiku, search_query = opus_orchestrator(objective, file_content, previous_results, use_search)
213
+ else:
214
+ opus_result, _, search_query = opus_orchestrator(objective, previous_results=previous_results, use_search=use_search)
215
+
216
+ if "The task is complete:" in opus_result:
217
+ # If Opus indicates the task is complete, exit the loop
218
+ final_output = opus_result.replace("The task is complete:", "").strip()
219
+ break
220
+ else:
221
+ sub_task_prompt = opus_result
222
+ # Append file content to the prompt for the initial call to haiku_sub_agent, if applicable
223
+ if file_content_for_haiku and not haiku_tasks:
224
+ sub_task_prompt = f"{sub_task_prompt}\n\nFile content:\n{file_content_for_haiku}"
225
+ # Call haiku_sub_agent with the prepared prompt, search query, and record the result
226
+ sub_task_result = haiku_sub_agent(sub_task_prompt, search_query, haiku_tasks, use_search)
227
+ # Log the task and its result for future reference
228
+ haiku_tasks.append({"task": sub_task_prompt, "result": sub_task_result})
229
+ # Record the exchange for processing and output generation
230
+ task_exchanges.append((sub_task_prompt, sub_task_result))
231
+ # Prevent file content from being included in future haiku_sub_agent calls
232
+ file_content_for_haiku = None
233
+
234
+ # Create the .md filename
235
+ sanitized_objective = re.sub(r'\W+', '_', objective)
236
+ timestamp = datetime.now().strftime("%H-%M-%S")
237
+
238
+ # Call Opus to review and refine the sub-task results
239
+ refined_output = opus_refine(objective, [result for _, result in task_exchanges], timestamp, sanitized_objective)
240
+
241
+ # Extract the project name from the refined output
242
+ project_name_match = re.search(r'Project Name: (.*)', refined_output)
243
+ project_name = project_name_match.group(1).strip() if project_name_match else sanitized_objective
244
+
245
+ # Extract the folder structure from the refined output
246
+ folder_structure_match = re.search(r'<folder_structure>(.*?)</folder_structure>', refined_output, re.DOTALL)
247
+ folder_structure = {}
248
+ if folder_structure_match:
249
+ json_string = folder_structure_match.group(1).strip()
250
+ try:
251
+ folder_structure = json.loads(json_string)
252
+ except json.JSONDecodeError as e:
253
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
254
+ console.print(Panel(f"Invalid JSON string: [bold]{json_string}[/bold]", title="[bold red]Invalid JSON String[/bold red]", title_align="left", border_style="red"))
255
+
256
+ # Extract code files from the refined output
257
+ code_blocks = re.findall(r'Filename: (\S+)\s*```[\w]*\n(.*?)\n```', refined_output, re.DOTALL)
258
+
259
+ # Create the folder structure and code files
260
+ create_folder_structure(project_name, folder_structure, code_blocks)
261
+
262
+ # Truncate the sanitized_objective to a maximum of 50 characters
263
+ max_length = 25
264
+ truncated_objective = sanitized_objective[:max_length] if len(sanitized_objective) > max_length else sanitized_objective
265
+
266
+ # Update the filename to include the project name
267
+ filename = f"{timestamp}_{truncated_objective}.md"
268
+
269
+ # Prepare the full exchange log
270
+ exchange_log = f"Objective: {objective}\n\n"
271
+ exchange_log += "=" * 40 + " Task Breakdown " + "=" * 40 + "\n\n"
272
+ for i, (prompt, result) in enumerate(task_exchanges, start=1):
273
+ exchange_log += f"Task {i}:\n"
274
+ exchange_log += f"Prompt: {prompt}\n"
275
+ exchange_log += f"Result: {result}\n\n"
276
+
277
+ exchange_log += "=" * 40 + " Refined Final Output " + "=" * 40 + "\n\n"
278
+ exchange_log += refined_output
279
+
280
+ console.print(f"\n[bold]Refined Final output:[/bold]\n{refined_output}")
281
+
282
+ with open(filename, 'w') as file:
283
+ file.write(exchange_log)
284
+ print(f"\nFull exchange log saved to {filename}")
maestro-ollama.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ from datetime import datetime
4
+ import json
5
+ from rich.console import Console
6
+ from rich.panel import Panel
7
+ import ollama
8
+ from ollama import Client # Import the Ollama client
9
+ import argparse
10
+
11
+ # Only for the first time run based on the model you want to use
12
+ # ollama.pull('llama3:70b')
13
+ # ollama.pull('llama3:8b')
14
+ # ollama.pull('llama3:8b')
15
+ # ollama.pull('llama3:70b-instruct')
16
+ # ollama.pull('llama3:instruct')
17
+
18
+ # Define model identifiers as variables at the top of the script
19
+ ORCHESTRATOR_MODEL = 'llama3:70b-instruct'
20
+ SUBAGENT_MODEL = 'llama3:instruct'
21
+ REFINER_MODEL = 'llama3:70b-instruct'
22
+
23
+ # check and pull models if they don't exist yet
24
+ for model in [ORCHESTRATOR_MODEL, SUBAGENT_MODEL, REFINER_MODEL]:
25
+ try:
26
+ print(f"Checking for model: {model}")
27
+ m = ollama.show(model)
28
+ except ollama._types.ResponseError as e:
29
+ print(f"Pulling model from ollama: {model}")
30
+ ollama.pull(model)
31
+
32
+ # Initialize the Ollama client
33
+ client = Client(host='http://localhost:11434')
34
+
35
+ console = Console()
36
+
37
+ def opus_orchestrator(objective, file_content=None, previous_results=None):
38
+ console.print(f"\n[bold]Calling Ollama Orchestrator for your objective[/bold]")
39
+ previous_results_text = "\n".join(previous_results) if previous_results else "None"
40
+ if file_content:
41
+ console.print(Panel(f"File content:\n{file_content}", title="[bold blue]File Content[/bold blue]", title_align="left", border_style="blue"))
42
+
43
+ response = client.chat(
44
+ model=ORCHESTRATOR_MODEL,
45
+ messages=[
46
+ {
47
+ "role": "user",
48
+ "content": f"Based on the following objective{' and file content' if file_content else ''}, and the previous sub-task results (if any), please break down the objective into the next sub-task, and create a concise and detailed prompt for a subagent so it can execute that task. Focus solely on the objective and avoid engaging in casual conversation with the subagent.\n\nWhen dealing with code tasks, make sure to check the code for errors and provide fixes and support as part of the next sub-task. If you find any bugs or have suggestions for better code, please include them in the next sub-task prompt.\n\nPlease assess if the objective has been fully achieved. If the previous sub-task results comprehensively address all aspects of the objective, include the phrase 'The task is complete:' at the beginning of your response. If the objective is not yet fully achieved, break it down into the next sub-task and create a concise and detailed prompt for a subagent to execute that task.\n\nObjective: {objective}" + (f'\nFile content:\n{file_content}' if file_content else '') + f"\n\nPrevious sub-task results:\n{previous_results_text}"
49
+ }
50
+ ]
51
+ )
52
+
53
+ response_text = response['message']['content']
54
+ console.print(Panel(response_text, title="[bold green]Ollama Orchestrator[/bold green]", title_align="left", border_style="green", subtitle="Sending task to Ollama sub-agent 👇"))
55
+ return response_text, file_content
56
+
57
+ def haiku_sub_agent(prompt, previous_haiku_tasks=None, continuation=False):
58
+ if previous_haiku_tasks is None:
59
+ previous_haiku_tasks = []
60
+
61
+ continuation_prompt = "Continuing from the previous answer, please complete the response."
62
+ if continuation:
63
+ prompt = continuation_prompt
64
+
65
+ # Compile previous tasks into a readable format
66
+ previous_tasks_summary = "Previous Sub-agent tasks:\n" + "\n".join(f"Task: {task['task']}\nResult: {task['result']}" for task in previous_haiku_tasks)
67
+
68
+ # Append previous tasks summary to the prompt
69
+ full_prompt = f"{previous_tasks_summary}\n\n{prompt}"
70
+
71
+ # Ensure prompt is not empty
72
+ if not full_prompt.strip():
73
+ raise ValueError("Prompt cannot be empty")
74
+
75
+ response = client.chat(
76
+ model=SUBAGENT_MODEL,
77
+ messages=[{"role": "user", "content": full_prompt}]
78
+ )
79
+
80
+ response_text = response['message']['content']
81
+
82
+ if len(response_text) >= 4000: # Threshold set to 4000 as a precaution
83
+ console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
84
+ continuation_response_text = haiku_sub_agent(continuation_prompt, previous_haiku_tasks, continuation=True)
85
+ response_text += continuation_response_text
86
+
87
+ console.print(Panel(response_text, title="[bold blue]Ollama Sub-agent Result[/bold blue]", title_align="left", border_style="blue", subtitle="Task completed, sending result to Ollama Orchestrator 👇"))
88
+ return response_text
89
+
90
+ def opus_refine(objective, sub_task_results, filename, projectname, continuation=False):
91
+ console.print("\nCalling Ollama to provide the refined final output for your objective:")
92
+
93
+ response = client.chat(
94
+ model=REFINER_MODEL,
95
+ messages=[
96
+ {
97
+ "role": "user",
98
+ "content": "Objective: " + objective + "\n\nSub-task results:\n" + "\n".join(sub_task_results) + "\n\nPlease review and refine the sub-task results into a cohesive final output. Add any missing information or details as needed.\n\nWhen working on code projects, ONLY AND ONLY IF THE PROJECT IS CLEARLY A CODING ONE, please provide the following:\n\n1. Project Name: Create a concise and appropriate project name that fits the project based on what it's creating. The project name should be no more than 20 characters long.\n\n2. Folder Structure: Provide the folder structure as a valid JSON object, where each key represents a folder or file, and nested keys represent subfolders. Use null values for files. Ensure the JSON is properly formatted without any syntax errors. Please make sure all keys are enclosed in double quotes, and ensure objects are correctly encapsulated with braces, separating items with commas as necessary. Wrap the JSON object in <folder_structure> tags.\n\n3. Code Files: For each code file, include ONLY the file name, NEVER EVER USE THE FILE PATH OR ANY OTHER FORMATTING. YOU ONLY USE THE FOLLOWING format 'Filename: <filename>' followed by the code block enclosed in triple backticks, with the language identifier after the opening backticks, like this:\n\npython\n<code>\n\n\nFocus solely on the objective and avoid engaging in casual conversation. Ensure the final output is clear, concise, and addresses all aspects of the objective.​"
99
+ }
100
+ ]
101
+ )
102
+
103
+ response_text = response['message']['content']
104
+
105
+ if len(response_text) >= 4000: # Threshold set to 4000 as a precaution
106
+ console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
107
+ continuation_response_text = opus_refine(objective, sub_task_results, filename, projectname, continuation=True)
108
+ response_text += continuation_response_text
109
+
110
+ console.print(Panel(response_text, title="[bold green]Final Output[/bold green]", title_align="left", border_style="green"))
111
+ return response_text
112
+
113
+ def create_folder_structure(project_name, folder_structure, code_blocks):
114
+ try:
115
+ os.makedirs(project_name, exist_ok=True)
116
+ console.print(Panel(f"Created project folder: [bold]{project_name}[/bold]", title="[bold blue]Project Folder Creation[/bold blue]", title_align="left", border_style="blue"))
117
+ except OSError as e:
118
+ console.print(Panel(f"Error creating project folder: [bold]{project_name}[/bold]\nError: {e}", title="[bold red]Project Folder Creation Error[/bold red]", title_align="left", border_style="red"))
119
+
120
+ create_folders_and_files(project_name, folder_structure, code_blocks)
121
+
122
+ def create_folders_and_files(current_path, structure, code_blocks):
123
+ for key, value in structure.items():
124
+ path = os.path.join(current_path, key)
125
+ if isinstance(value, dict):
126
+ try:
127
+ os.makedirs(path, exist_ok=True)
128
+ console.print(Panel(f"Created folder: [bold]{path}[/bold]", title="[bold blue]Folder Creation[/bold blue]", title_align="left", border_style="blue"))
129
+ create_folders_and_files(path, value, code_blocks)
130
+ except OSError as e:
131
+ console.print(Panel(f"Error creating folder: [bold]{path}[/bold]\nError: {e}", title="[bold red]Folder Creation Error[/bold red]", title_align="left", border_style="red"))
132
+ else:
133
+ code_content = next((code for file, code in code_blocks if file == key), None)
134
+ if code_content:
135
+ try:
136
+ with open(path, 'w') as file:
137
+ file.write(code_content)
138
+ console.print(Panel(f"Created file: [bold]{path}[/bold]", title="[bold green]File Creation[/bold green]", title_align="left", border_style="green"))
139
+ except IOError as e:
140
+ console.print(Panel(f"Error creating file: [bold]{path}[/bold]\nError: {e}", title="[bold red]File Creation Error[/bold red]", title_align="left", border_style="red"))
141
+ else:
142
+ console.print(Panel(f"Code content not found for file: [bold]{key}[/bold]", title="[bold yellow]Missing Code Content[/bold yellow]", title_align="left", border_style="yellow"))
143
+
144
+ def read_file(file_path):
145
+ with open(file_path, 'r') as file:
146
+ content = file.read()
147
+ return content
148
+
149
+ def has_task_data():
150
+ return os.path.exists('task_data.json')
151
+
152
+ def read_task_data():
153
+ with open('task_data.json', 'r') as file:
154
+ task_data = json.load(file)
155
+ return task_data
156
+
157
+
158
+ def write_task_data(task_data):
159
+ with open('task_data.json', 'w') as file:
160
+ json.dump(task_data, file)
161
+
162
+
163
+ continue_from_last_task = False
164
+ tmp_task_data = {}
165
+
166
+ # parse args
167
+ parser = argparse.ArgumentParser()
168
+ parser.add_argument('-p', '--prompt', type=str, help='Please enter your objective with or without a text file path')
169
+ args = parser.parse_args()
170
+
171
+ if args.prompt is not None:
172
+ objective = args.prompt
173
+ else:
174
+ # Check if there is a task data file
175
+ if has_task_data():
176
+ continue_from_last_task = input("Do you want to continue from the last task? (y/n): ").lower() == 'y'
177
+
178
+ if continue_from_last_task:
179
+ tmp_task_data = read_task_data()
180
+ objective = tmp_task_data['objective']
181
+ task_exchanges = tmp_task_data['task_exchanges']
182
+ console.print(Panel(f"Resuming from last task: {objective}", title="[bold blue]Resuming from last task[/bold blue]", title_align="left", border_style="blue"))
183
+ else:
184
+ # Get the objective from user input
185
+ objective = input("Please enter your objective with or without a text file path: ")
186
+ tmp_task_data['objective'] = objective
187
+ tmp_task_data['task_exchanges'] = []
188
+
189
+ # Check if the input contains a file path
190
+ if "./" in objective or "/" in objective:
191
+ # Extract the file path from the objective
192
+ file_path = re.findall(r'[./\w]+\.[\w]+', objective)[0]
193
+ # Read the file content
194
+ with open(file_path, 'r') as file:
195
+ file_content = file.read()
196
+ # Update the objective string to remove the file path
197
+ objective = objective.split(file_path)[0].strip()
198
+ else:
199
+ file_content = None
200
+
201
+ task_exchanges = []
202
+ haiku_tasks = []
203
+
204
+ while True:
205
+ # Call Orchestrator to break down the objective into the next sub-task or provide the final output
206
+ previous_results = [result for _, result in task_exchanges]
207
+ if not task_exchanges:
208
+ # Pass the file content only in the first iteration if available
209
+ opus_result, file_content_for_haiku = opus_orchestrator(objective, file_content, previous_results)
210
+ else:
211
+ opus_result, _ = opus_orchestrator(objective, previous_results=previous_results)
212
+
213
+ if "The task is complete:" in opus_result:
214
+ # If Opus indicates the task is complete, exit the loop
215
+ final_output = opus_result.replace("The task is complete:", "").strip()
216
+ break
217
+ else:
218
+ sub_task_prompt = opus_result
219
+ # Append file content to the prompt for the initial call to haiku_sub_agent, if applicable
220
+ if file_content_for_haiku and not haiku_tasks:
221
+ sub_task_prompt = f"{sub_task_prompt}\n\nFile content:\n{file_content_for_haiku}"
222
+ # Call haiku_sub_agent with the prepared prompt and record the result
223
+ sub_task_result = haiku_sub_agent(sub_task_prompt, haiku_tasks)
224
+ # Log the task and its result for future reference
225
+ haiku_tasks.append({"task": sub_task_prompt, "result": sub_task_result})
226
+ # Record the exchange for processing and output generation
227
+ task_exchanges.append((sub_task_prompt, sub_task_result))
228
+ # Update the task data with the new task exchanges
229
+ tmp_task_data['task_exchanges'] = task_exchanges
230
+ # Save the task data to a JSON file for resuming later
231
+ write_task_data(tmp_task_data)
232
+ # Prevent file content from being included in future haiku_sub_agent calls
233
+ file_content_for_haiku = None
234
+
235
+ # Create the .md filename
236
+ sanitized_objective = re.sub(r'\W+', '_', objective)
237
+ timestamp = datetime.now().strftime("%H-%M-%S")
238
+
239
+ # Call Opus to review and refine the sub-task results
240
+ refined_output = opus_refine(objective, [result for _, result in task_exchanges], timestamp, sanitized_objective)
241
+
242
+ # Extract the project name from the refined output
243
+ project_name_match = re.search(r'Project Name: (.*)', refined_output)
244
+ project_name = project_name_match.group(1).strip() if project_name_match else sanitized_objective
245
+
246
+ # Extract the folder structure from the refined output
247
+ folder_structure_match = re.search(r'<folder_structure>(.*?)</folder_structure>', refined_output, re.DOTALL)
248
+ folder_structure = {}
249
+ if folder_structure_match:
250
+ json_string = folder_structure_match.group(1).strip()
251
+ try:
252
+ folder_structure = json.loads(json_string)
253
+ except json.JSONDecodeError as e:
254
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
255
+ console.print(Panel(f"Invalid JSON string: [bold]{json_string}[/bold]", title="[bold red]Invalid JSON String[/bold red]", title_align="left", border_style="red"))
256
+
257
+ # Extract code files from the refined output
258
+ code_blocks = re.findall(r'Filename: (\S+)\s*```[\w]*\n(.*?)\n```', refined_output, re.DOTALL)
259
+
260
+ # Create the folder structure and code files
261
+ create_folder_structure(project_name, folder_structure, code_blocks)
262
+
263
+ # Truncate the sanitized_objective to a maximum of 50 characters
264
+ max_length = 25
265
+ truncated_objective = sanitized_objective[:max_length] if len(sanitized_objective) > max_length else sanitized_objective
266
+
267
+ # Update the filename to include the project name
268
+ filename = f"{timestamp}_{truncated_objective}.md"
269
+
270
+ # Prepare the full exchange log
271
+ exchange_log = f"Objective: {objective}\n\n"
272
+ exchange_log += "=" * 40 + " Task Breakdown " + "=" * 40 + "\n\n"
273
+ for i, (prompt, result) in enumerate(task_exchanges, start=1):
274
+ exchange_log += f"Task {i}:\n"
275
+ exchange_log += f"Prompt: {prompt}\n"
276
+ exchange_log += f"Result: {result}\n\n"
277
+
278
+ exchange_log += "=" * 40 + " Refined Final Output " + "=" * 40 + "\n\n"
279
+ exchange_log += refined_output
280
+
281
+ console.print(f"\n[bold]Refined Final output:[/bold]\n{refined_output}")
282
+
283
+ with open(filename, 'w') as file:
284
+ file.write(exchange_log)
285
+ print(f"\nFull exchange log saved to {filename}")
maestro.py ADDED
@@ -0,0 +1,304 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from anthropic import Anthropic
3
+ import re
4
+ from rich.console import Console
5
+ from rich.panel import Panel
6
+ from datetime import datetime
7
+ import json
8
+ from tavily import TavilyClient
9
+
10
+ # Set up the Anthropic API client
11
+ client = Anthropic(api_key="YOUR API KEY")
12
+
13
+ # Available Claude models:
14
+ # Claude 3 Opus claude-3-opus-20240229
15
+ # Claude 3 Sonnet claude-3-sonnet-20240229
16
+ # Claude 3 Haiku claude-3-haiku-20240307
17
+
18
+ ORCHESTRATOR_MODEL = "claude-3-opus-20240229"
19
+ SUB_AGENT_MODEL = "claude-3-sonnet-20240229"
20
+ REFINER_MODEL = "claude-3-opus-20240229"
21
+
22
+ def calculate_subagent_cost(model, input_tokens, output_tokens):
23
+ # Pricing information per model
24
+ pricing = {
25
+ "claude-3-opus-20240229": {"input_cost_per_mtok": 15.00, "output_cost_per_mtok": 75.00},
26
+ "claude-3-haiku-20240307": {"input_cost_per_mtok": 0.25, "output_cost_per_mtok": 1.25},
27
+ "claude-3-sonnet-20240229": {"input_cost_per_mtok": 3.00, "output_cost_per_mtok": 15.00},
28
+ }
29
+
30
+ # Calculate cost
31
+ input_cost = (input_tokens / 1_000_000) * pricing[model]["input_cost_per_mtok"]
32
+ output_cost = (output_tokens / 1_000_000) * pricing[model]["output_cost_per_mtok"]
33
+ total_cost = input_cost + output_cost
34
+
35
+ return total_cost
36
+
37
+ # Initialize the Rich Console
38
+ console = Console()
39
+
40
+ def opus_orchestrator(objective, file_content=None, previous_results=None, use_search=False):
41
+ console.print(f"\n[bold]Calling Orchestrator for your objective[/bold]")
42
+ previous_results_text = "\n".join(previous_results) if previous_results else "None"
43
+ if file_content:
44
+ console.print(Panel(f"File content:\n{file_content}", title="[bold blue]File Content[/bold blue]", title_align="left", border_style="blue"))
45
+
46
+ messages = [
47
+ {
48
+ "role": "user",
49
+ "content": [
50
+ {"type": "text", "text": f"Based on the following objective{' and file content' if file_content else ''}, and the previous sub-task results (if any), please break down the objective into the next sub-task, and create a concise and detailed prompt for a subagent so it can execute that task. IMPORTANT!!! when dealing with code tasks make sure you check the code for errors and provide fixes and support as part of the next sub-task. If you find any bugs or have suggestions for better code, please include them in the next sub-task prompt. Please assess if the objective has been fully achieved. If the previous sub-task results comprehensively address all aspects of the objective, include the phrase 'The task is complete:' at the beginning of your response. If the objective is not yet fully achieved, break it down into the next sub-task and create a concise and detailed prompt for a subagent to execute that task.:\n\nObjective: {objective}" + ('\\nFile content:\\n' + file_content if file_content else '') + f"\n\nPrevious sub-task results:\n{previous_results_text}"}
51
+ ]
52
+ }
53
+ ]
54
+ if use_search:
55
+ messages[0]["content"].append({"type": "text", "text": "Please also generate a JSON object containing a single 'search_query' key, which represents a question that, when asked online, would yield important information for solving the subtask. The question should be specific and targeted to elicit the most relevant and helpful resources. Format your JSON like this, with no additional text before or after:\n{\"search_query\": \"<question>\"}\n"})
56
+
57
+ opus_response = client.messages.create(
58
+ model=ORCHESTRATOR_MODEL,
59
+ max_tokens=4096,
60
+ messages=messages
61
+ )
62
+
63
+ response_text = opus_response.content[0].text
64
+ console.print(f"Input Tokens: {opus_response.usage.input_tokens}, Output Tokens: {opus_response.usage.output_tokens}")
65
+ total_cost = calculate_subagent_cost(ORCHESTRATOR_MODEL, opus_response.usage.input_tokens, opus_response.usage.output_tokens)
66
+ console.print(f"Orchestrator Cost: ${total_cost:.4f}")
67
+
68
+ search_query = None
69
+ if use_search:
70
+ # Extract the JSON from the response
71
+ json_match = re.search(r'{.*}', response_text, re.DOTALL)
72
+ if json_match:
73
+ json_string = json_match.group()
74
+ try:
75
+ search_query = json.loads(json_string)["search_query"]
76
+ console.print(Panel(f"Search Query: {search_query}", title="[bold blue]Search Query[/bold blue]", title_align="left", border_style="blue"))
77
+ response_text = response_text.replace(json_string, "").strip()
78
+ except json.JSONDecodeError as e:
79
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
80
+ console.print(Panel(f"Skipping search query extraction.", title="[bold yellow]Search Query Extraction Skipped[/bold yellow]", title_align="left", border_style="yellow"))
81
+ else:
82
+ search_query = None
83
+
84
+ console.print(Panel(response_text, title=f"[bold green]Opus Orchestrator[/bold green]", title_align="left", border_style="green", subtitle="Sending task to Haiku 👇"))
85
+ return response_text, file_content, search_query
86
+
87
+
88
+ def haiku_sub_agent(prompt, search_query=None, previous_haiku_tasks=None, use_search=False, continuation=False):
89
+ if previous_haiku_tasks is None:
90
+ previous_haiku_tasks = []
91
+
92
+ continuation_prompt = "Continuing from the previous answer, please complete the response."
93
+ system_message = "Previous Haiku tasks:\n" + "\n".join(f"Task: {task['task']}\nResult: {task['result']}" for task in previous_haiku_tasks)
94
+ if continuation:
95
+ prompt = continuation_prompt
96
+
97
+ qna_response = None
98
+ if search_query and use_search:
99
+ # Initialize the Tavily client
100
+ tavily = TavilyClient(api_key="YOUR API KEY HERE")
101
+ # Perform a QnA search based on the search query
102
+ qna_response = tavily.qna_search(query=search_query)
103
+ console.print(f"QnA response: {qna_response}", style="yellow")
104
+
105
+ # Prepare the messages array with only the prompt initially
106
+ messages = [
107
+ {
108
+ "role": "user",
109
+ "content": [{"type": "text", "text": prompt}]
110
+ }
111
+ ]
112
+
113
+ # Add search results to the messages if there are any
114
+ if qna_response:
115
+ messages[0]["content"].append({"type": "text", "text": f"\nSearch Results:\n{qna_response}"})
116
+
117
+ haiku_response = client.messages.create(
118
+ model=SUB_AGENT_MODEL,
119
+ max_tokens=4096,
120
+ messages=messages,
121
+ system=system_message
122
+ )
123
+
124
+ response_text = haiku_response.content[0].text
125
+ console.print(f"Input Tokens: {haiku_response.usage.input_tokens}, Output Tokens: {haiku_response.usage.output_tokens}")
126
+ total_cost = calculate_subagent_cost(SUB_AGENT_MODEL, haiku_response.usage.input_tokens, haiku_response.usage.output_tokens)
127
+ console.print(f"Sub-agent Cost: ${total_cost:.4f}")
128
+
129
+ if haiku_response.usage.output_tokens >= 4000: # Threshold set to 4000 as a precaution
130
+ console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
131
+ continuation_response_text = haiku_sub_agent(prompt, search_query, previous_haiku_tasks, use_search, continuation=True)
132
+ response_text += continuation_response_text
133
+
134
+ console.print(Panel(response_text, title="[bold blue]Haiku Sub-agent Result[/bold blue]", title_align="left", border_style="blue", subtitle="Task completed, sending result to Opus 👇"))
135
+ return response_text
136
+
137
+ def opus_refine(objective, sub_task_results, filename, projectname, continuation=False):
138
+ print("\nCalling Opus to provide the refined final output for your objective:")
139
+ messages = [
140
+ {
141
+ "role": "user",
142
+ "content": [
143
+ {"type": "text", "text": "Objective: " + objective + "\n\nSub-task results:\n" + "\n".join(sub_task_results) + "\n\nPlease review and refine the sub-task results into a cohesive final output. Add any missing information or details as needed. When working on code projects, ONLY AND ONLY IF THE PROJECT IS CLEARLY A CODING ONE please provide the following:\n1. Project Name: Create a concise and appropriate project name that fits the project based on what it's creating. The project name should be no more than 20 characters long.\n2. Folder Structure: Provide the folder structure as a valid JSON object, where each key represents a folder or file, and nested keys represent subfolders. Use null values for files. Ensure the JSON is properly formatted without any syntax errors. Please make sure all keys are enclosed in double quotes, and ensure objects are correctly encapsulated with braces, separating items with commas as necessary.\nWrap the JSON object in <folder_structure> tags.\n3. Code Files: For each code file, include ONLY the file name NEVER EVER USE THE FILE PATH OR ANY OTHER FORMATTING YOU ONLY USE THE FOLLOWING format 'Filename: <filename>' followed by the code block enclosed in triple backticks, with the language identifier after the opening backticks, like this:\n\n​python\n<code>\n​"}
144
+ ]
145
+ }
146
+ ]
147
+
148
+ opus_response = client.messages.create(
149
+ model=REFINER_MODEL,
150
+ max_tokens=4096,
151
+ messages=messages
152
+ )
153
+
154
+ response_text = opus_response.content[0].text.strip()
155
+ console.print(f"Input Tokens: {opus_response.usage.input_tokens}, Output Tokens: {opus_response.usage.output_tokens}")
156
+ total_cost = calculate_subagent_cost(REFINER_MODEL, opus_response.usage.input_tokens, opus_response.usage.output_tokens)
157
+ console.print(f"Refine Cost: ${total_cost:.4f}")
158
+
159
+ if opus_response.usage.output_tokens >= 4000 and not continuation: # Threshold set to 4000 as a precaution
160
+ console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
161
+ continuation_response_text = opus_refine(objective, sub_task_results + [response_text], filename, projectname, continuation=True)
162
+ response_text += "\n" + continuation_response_text
163
+
164
+ console.print(Panel(response_text, title="[bold green]Final Output[/bold green]", title_align="left", border_style="green"))
165
+ return response_text
166
+
167
+ def create_folder_structure(project_name, folder_structure, code_blocks):
168
+ # Create the project folder
169
+ try:
170
+ os.makedirs(project_name, exist_ok=True)
171
+ console.print(Panel(f"Created project folder: [bold]{project_name}[/bold]", title="[bold green]Project Folder[/bold green]", title_align="left", border_style="green"))
172
+ except OSError as e:
173
+ console.print(Panel(f"Error creating project folder: [bold]{project_name}[/bold]\nError: {e}", title="[bold red]Project Folder Creation Error[/bold red]", title_align="left", border_style="red"))
174
+ return
175
+
176
+ # Recursively create the folder structure and files
177
+ create_folders_and_files(project_name, folder_structure, code_blocks)
178
+
179
+ def create_folders_and_files(current_path, structure, code_blocks):
180
+ for key, value in structure.items():
181
+ path = os.path.join(current_path, key)
182
+ if isinstance(value, dict):
183
+ try:
184
+ os.makedirs(path, exist_ok=True)
185
+ console.print(Panel(f"Created folder: [bold]{path}[/bold]", title="[bold blue]Folder Creation[/bold blue]", title_align="left", border_style="blue"))
186
+ create_folders_and_files(path, value, code_blocks)
187
+ except OSError as e:
188
+ console.print(Panel(f"Error creating folder: [bold]{path}[/bold]\nError: {e}", title="[bold red]Folder Creation Error[/bold red]", title_align="left", border_style="red"))
189
+ else:
190
+ code_content = next((code for file, code in code_blocks if file == key), None)
191
+ if code_content:
192
+ try:
193
+ with open(path, 'w') as file:
194
+ file.write(code_content)
195
+ console.print(Panel(f"Created file: [bold]{path}[/bold]", title="[bold green]File Creation[/bold green]", title_align="left", border_style="green"))
196
+ except IOError as e:
197
+ console.print(Panel(f"Error creating file: [bold]{path}[/bold]\nError: {e}", title="[bold red]File Creation Error[/bold red]", title_align="left", border_style="red"))
198
+ else:
199
+ console.print(Panel(f"Code content not found for file: [bold]{key}[/bold]", title="[bold yellow]Missing Code Content[/bold yellow]", title_align="left", border_style="yellow"))
200
+
201
+ def read_file(file_path):
202
+ with open(file_path, 'r') as file:
203
+ content = file.read()
204
+ return content
205
+
206
+ # Get the objective from user input
207
+ objective = input("Please enter your objective with or without a text file path: ")
208
+
209
+ # Check if the input contains a file path
210
+ if "./" in objective or "/" in objective:
211
+ # Extract the file path from the objective
212
+ file_path = re.findall(r'[./\w]+\.[\w]+', objective)[0]
213
+ # Read the file content
214
+ with open(file_path, 'r') as file:
215
+ file_content = file.read()
216
+ # Update the objective string to remove the file path
217
+ objective = objective.split(file_path)[0].strip()
218
+ else:
219
+ file_content = None
220
+
221
+ # Ask the user if they want to use search
222
+ use_search = input("Do you want to use search? (y/n): ").lower() == 'y'
223
+
224
+ task_exchanges = []
225
+ haiku_tasks = []
226
+
227
+ while True:
228
+ # Call Orchestrator to break down the objective into the next sub-task or provide the final output
229
+ previous_results = [result for _, result in task_exchanges]
230
+ if not task_exchanges:
231
+ # Pass the file content only in the first iteration if available
232
+ opus_result, file_content_for_haiku, search_query = opus_orchestrator(objective, file_content, previous_results, use_search)
233
+ else:
234
+ opus_result, _, search_query = opus_orchestrator(objective, previous_results=previous_results, use_search=use_search)
235
+
236
+ if "The task is complete:" in opus_result:
237
+ # If Opus indicates the task is complete, exit the loop
238
+ final_output = opus_result.replace("The task is complete:", "").strip()
239
+ break
240
+ else:
241
+ sub_task_prompt = opus_result
242
+ # Append file content to the prompt for the initial call to haiku_sub_agent, if applicable
243
+ if file_content_for_haiku and not haiku_tasks:
244
+ sub_task_prompt = f"{sub_task_prompt}\n\nFile content:\n{file_content_for_haiku}"
245
+ # Call haiku_sub_agent with the prepared prompt, search query, and record the result
246
+ sub_task_result = haiku_sub_agent(sub_task_prompt, search_query, haiku_tasks, use_search)
247
+ # Log the task and its result for future reference
248
+ haiku_tasks.append({"task": sub_task_prompt, "result": sub_task_result})
249
+ # Record the exchange for processing and output generation
250
+ task_exchanges.append((sub_task_prompt, sub_task_result))
251
+ # Prevent file content from being included in future haiku_sub_agent calls
252
+ file_content_for_haiku = None
253
+
254
+ # Create the .md filename
255
+ sanitized_objective = re.sub(r'\W+', '_', objective)
256
+ timestamp = datetime.now().strftime("%H-%M-%S")
257
+
258
+ # Call Opus to review and refine the sub-task results
259
+ refined_output = opus_refine(objective, [result for _, result in task_exchanges], timestamp, sanitized_objective)
260
+
261
+ # Extract the project name from the refined output
262
+ project_name_match = re.search(r'Project Name: (.*)', refined_output)
263
+ project_name = project_name_match.group(1).strip() if project_name_match else sanitized_objective
264
+
265
+ # Extract the folder structure from the refined output
266
+ folder_structure_match = re.search(r'<folder_structure>(.*?)</folder_structure>', refined_output, re.DOTALL)
267
+ folder_structure = {}
268
+ if folder_structure_match:
269
+ json_string = folder_structure_match.group(1).strip()
270
+ try:
271
+ folder_structure = json.loads(json_string)
272
+ except json.JSONDecodeError as e:
273
+ console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
274
+ console.print(Panel(f"Invalid JSON string: [bold]{json_string}[/bold]", title="[bold red]Invalid JSON String[/bold red]", title_align="left", border_style="red"))
275
+
276
+ # Extract code files from the refined output
277
+ code_blocks = re.findall(r'Filename: (\S+)\s*```[\w]*\n(.*?)\n```', refined_output, re.DOTALL)
278
+
279
+ # Create the folder structure and code files
280
+ create_folder_structure(project_name, folder_structure, code_blocks)
281
+
282
+ # Truncate the sanitized_objective to a maximum of 50 characters
283
+ max_length = 25
284
+ truncated_objective = sanitized_objective[:max_length] if len(sanitized_objective) > max_length else sanitized_objective
285
+
286
+ # Update the filename to include the project name
287
+ filename = f"{timestamp}_{truncated_objective}.md"
288
+
289
+ # Prepare the full exchange log
290
+ exchange_log = f"Objective: {objective}\n\n"
291
+ exchange_log += "=" * 40 + " Task Breakdown " + "=" * 40 + "\n\n"
292
+ for i, (prompt, result) in enumerate(task_exchanges, start=1):
293
+ exchange_log += f"Task {i}:\n"
294
+ exchange_log += f"Prompt: {prompt}\n"
295
+ exchange_log += f"Result: {result}\n\n"
296
+
297
+ exchange_log += "=" * 40 + " Refined Final Output " + "=" * 40 + "\n\n"
298
+ exchange_log += refined_output
299
+
300
+ console.print(f"\n[bold]Refined Final output:[/bold]\n{refined_output}")
301
+
302
+ with open(filename, 'w') as file:
303
+ file.write(exchange_log)
304
+ print(f"\nFull exchange log saved to {filename}")
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ anthropic
2
+ rich
3
+ tavily-python
4
+ ollama
5
+ groq