Spaces:
Configuration error
Configuration error
Base
Browse files1337 Based Ai
- README.md +171 -11
- maestro-gpt.py +236 -0
- maestro-groq.py +235 -0
- maestro-lmstudio.py +284 -0
- maestro-ollama.py +285 -0
- maestro.py +304 -0
- requirements.txt +5 -0
README.md
CHANGED
@@ -1,11 +1,171 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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\npython\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\npython\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\npython\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\npython\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
|