• Maestro实践


    GitHub - Doriandarko/maestro: A framework for Claude Opus to intelligently orchestrate subagents.

    初步使用了一下,可能是我使用不当,感觉并没有很厉害

    1)克隆代码库

    git clone GitHub - Doriandarko/maestro: A framework for Claude Opus to intelligently orchestrate subagents. 2)我用的是ollama

    maestro-ollama.py

    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. # Only for the first time run based on the model you want to use
    11. # ollama.pull('llama3:70b')
    12. # ollama.pull('llama3:8b')
    13. # ollama.pull('llama3:8b')
    14. # ollama.pull('llama3:70b-instruct')
    15. # ollama.pull('llama3:instruct')
    16. # Define model identifiers as variables at the top of the script
    17. ORCHESTRATOR_MODEL = 'qwen:14b'
    18. SUBAGENT_MODEL = 'deepseek-coder:latest'
    19. REFINER_MODEL = 'llama3:latest'
    20. # check and pull models if they don't exist yet
    21. for model in [ORCHESTRATOR_MODEL, SUBAGENT_MODEL, REFINER_MODEL]:
    22. try:
    23. print(f"Checking for model: {model}")
    24. m = ollama.show(model)
    25. except ollama._types.ResponseError as e:
    26. print(f"Pulling model from ollama: {model}")
    27. ollama.pull(model)
    28. # Initialize the Ollama client
    29. client = Client(host='http://localhost:11434')
    30. console = Console()
    31. def opus_orchestrator(objective, file_content=None, previous_results=None):
    32. console.print(f"\n[bold]Calling Ollama Orchestrator for your objective[/bold]")
    33. previous_results_text = "\n".join(previous_results) if previous_results else "None"
    34. if file_content:
    35. console.print(Panel(f"File content:\n{file_content}", title="[bold blue]File Content[/bold blue]", title_align="left", border_style="blue"))
    36. response = client.chat(
    37. model=ORCHESTRATOR_MODEL,
    38. messages=[
    39. {
    40. "role": "user",
    41. "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}"
    42. }
    43. ]
    44. )
    45. response_text = response['message']['content']
    46. 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 👇"))
    47. return response_text, file_content
    48. def haiku_sub_agent(prompt, previous_haiku_tasks=None, continuation=False):
    49. if previous_haiku_tasks is None:
    50. previous_haiku_tasks = []
    51. continuation_prompt = "Continuing from the previous answer, please complete the response."
    52. if continuation:
    53. prompt = continuation_prompt
    54. # Compile previous tasks into a readable format
    55. previous_tasks_summary = "Previous Sub-agent tasks:\n" + "\n".join(f"Task: {task['task']}\nResult: {task['result']}" for task in previous_haiku_tasks)
    56. # Append previous tasks summary to the prompt
    57. full_prompt = f"{previous_tasks_summary}\n\n{prompt}"
    58. # Ensure prompt is not empty
    59. if not full_prompt.strip():
    60. raise ValueError("Prompt cannot be empty")
    61. response = client.chat(
    62. model=SUBAGENT_MODEL,
    63. messages=[{"role": "user", "content": full_prompt}]
    64. )
    65. response_text = response['message']['content']
    66. if len(response_text) >= 4000: # Threshold set to 4000 as a precaution
    67. console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
    68. continuation_response_text = haiku_sub_agent(continuation_prompt, previous_haiku_tasks, continuation=True)
    69. response_text += continuation_response_text
    70. 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 👇"))
    71. return response_text
    72. def opus_refine(objective, sub_task_results, filename, projectname, continuation=False):
    73. console.print("\nCalling Ollama to provide the refined final output for your objective:")
    74. response = client.chat(
    75. model=REFINER_MODEL,
    76. messages=[
    77. {
    78. "role": "user",
    79. "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 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: ' followed by the code block enclosed in triple backticks, with the language identifier after the opening backticks, like this:\n\npython\n\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.​"
    80. }
    81. ]
    82. )
    83. response_text = response['message']['content']
    84. if len(response_text) >= 4000: # Threshold set to 4000 as a precaution
    85. console.print("[bold yellow]Warning:[/bold yellow] Output may be truncated. Attempting to continue the response.")
    86. continuation_response_text = opus_refine(objective, sub_task_results, filename, projectname, continuation=True)
    87. response_text += continuation_response_text
    88. console.print(Panel(response_text, title="[bold green]Final Output[/bold green]", title_align="left", border_style="green"))
    89. return response_text
    90. def create_folder_structure(project_name, folder_structure, code_blocks):
    91. try:
    92. os.makedirs(project_name, exist_ok=True)
    93. 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"))
    94. except OSError as e:
    95. 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"))
    96. create_folders_and_files(project_name, folder_structure, code_blocks)
    97. def create_folders_and_files(current_path, structure, code_blocks):
    98. for key, value in structure.items():
    99. path = os.path.join(current_path, key)
    100. if isinstance(value, dict):
    101. try:
    102. os.makedirs(path, exist_ok=True)
    103. console.print(Panel(f"Created folder: [bold]{path}[/bold]", title="[bold blue]Folder Creation[/bold blue]", title_align="left", border_style="blue"))
    104. create_folders_and_files(path, value, code_blocks)
    105. except OSError as e:
    106. 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"))
    107. else:
    108. code_content = next((code for file, code in code_blocks if file == key), None)
    109. if code_content:
    110. try:
    111. with open(path, 'w') as file:
    112. file.write(code_content)
    113. console.print(Panel(f"Created file: [bold]{path}[/bold]", title="[bold green]File Creation[/bold green]", title_align="left", border_style="green"))
    114. except IOError as e:
    115. 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"))
    116. else:
    117. 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"))
    118. def read_file(file_path):
    119. with open(file_path, 'r') as file:
    120. content = file.read()
    121. return content
    122. def has_task_data():
    123. return os.path.exists('task_data.json')
    124. def read_task_data():
    125. with open('task_data.json', 'r') as file:
    126. task_data = json.load(file)
    127. return task_data
    128. def write_task_data(task_data):
    129. with open('task_data.json', 'w') as file:
    130. json.dump(task_data, file)
    131. continue_from_last_task = False
    132. tmp_task_data = {}
    133. # parse args
    134. parser = argparse.ArgumentParser()
    135. parser.add_argument('-p', '--prompt', type=str, help='Please enter your objective with or without a text file path')
    136. args = parser.parse_args()
    137. if args.prompt is not None:
    138. objective = args.prompt
    139. else:
    140. # Check if there is a task data file
    141. if has_task_data():
    142. continue_from_last_task = input("Do you want to continue from the last task? (y/n): ").lower() == 'y'
    143. if continue_from_last_task:
    144. tmp_task_data = read_task_data()
    145. objective = tmp_task_data['objective']
    146. task_exchanges = tmp_task_data['task_exchanges']
    147. console.print(Panel(f"Resuming from last task: {objective}", title="[bold blue]Resuming from last task[/bold blue]", title_align="left", border_style="blue"))
    148. else:
    149. # Get the objective from user input
    150. objective = input("Please enter your objective with or without a text file path: ")
    151. tmp_task_data['objective'] = objective
    152. tmp_task_data['task_exchanges'] = []
    153. # Check if the input contains a file path
    154. if "./" in objective or "/" in objective:
    155. # Extract the file path from the objective
    156. file_path = re.findall(r'[./\w]+\.[\w]+', objective)[0]
    157. # Read the file content
    158. with open(file_path, 'r') as file:
    159. file_content = file.read()
    160. # Update the objective string to remove the file path
    161. objective = objective.split(file_path)[0].strip()
    162. else:
    163. file_content = None
    164. task_exchanges = []
    165. haiku_tasks = []
    166. while True:
    167. # Call Orchestrator to break down the objective into the next sub-task or provide the final output
    168. previous_results = [result for _, result in task_exchanges]
    169. if not task_exchanges:
    170. # Pass the file content only in the first iteration if available
    171. opus_result, file_content_for_haiku = opus_orchestrator(objective, file_content, previous_results)
    172. else:
    173. opus_result, _ = opus_orchestrator(objective, previous_results=previous_results)
    174. if "The task is complete:" in opus_result:
    175. # If Opus indicates the task is complete, exit the loop
    176. final_output = opus_result.replace("The task is complete:", "").strip()
    177. break
    178. else:
    179. sub_task_prompt = opus_result
    180. # Append file content to the prompt for the initial call to haiku_sub_agent, if applicable
    181. if file_content_for_haiku and not haiku_tasks:
    182. sub_task_prompt = f"{sub_task_prompt}\n\nFile content:\n{file_content_for_haiku}"
    183. # Call haiku_sub_agent with the prepared prompt and record the result
    184. sub_task_result = haiku_sub_agent(sub_task_prompt, haiku_tasks)
    185. # Log the task and its result for future reference
    186. haiku_tasks.append({"task": sub_task_prompt, "result": sub_task_result})
    187. # Record the exchange for processing and output generation
    188. task_exchanges.append((sub_task_prompt, sub_task_result))
    189. # Update the task data with the new task exchanges
    190. tmp_task_data['task_exchanges'] = task_exchanges
    191. # Save the task data to a JSON file for resuming later
    192. write_task_data(tmp_task_data)
    193. # Prevent file content from being included in future haiku_sub_agent calls
    194. file_content_for_haiku = None
    195. # Create the .md filename
    196. sanitized_objective = re.sub(r'\W+', '_', objective)
    197. timestamp = datetime.now().strftime("%H-%M-%S")
    198. # Call Opus to review and refine the sub-task results
    199. refined_output = opus_refine(objective, [result for _, result in task_exchanges], timestamp, sanitized_objective)
    200. # Extract the project name from the refined output
    201. project_name_match = re.search(r'Project Name: (.*)', refined_output)
    202. project_name = project_name_match.group(1).strip() if project_name_match else sanitized_objective
    203. # Extract the folder structure from the refined output
    204. folder_structure_match = re.search(r'(.*?)', refined_output, re.DOTALL)
    205. folder_structure = {}
    206. if folder_structure_match:
    207. json_string = folder_structure_match.group(1).strip()
    208. try:
    209. folder_structure = json.loads(json_string)
    210. except json.JSONDecodeError as e:
    211. console.print(Panel(f"Error parsing JSON: {e}", title="[bold red]JSON Parsing Error[/bold red]", title_align="left", border_style="red"))
    212. 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"))
    213. # Extract code files from the refined output
    214. code_blocks = re.findall(r'Filename: (\S+)\s*```[\w]*\n(.*?)\n```', refined_output, re.DOTALL)
    215. # Create the folder structure and code files
    216. create_folder_structure(project_name, folder_structure, code_blocks)
    217. # Truncate the sanitized_objective to a maximum of 50 characters
    218. max_length = 25
    219. truncated_objective = sanitized_objective[:max_length] if len(sanitized_objective) > max_length else sanitized_objective
    220. # Update the filename to include the project name
    221. filename = f"{timestamp}_{truncated_objective}.md"
    222. # Prepare the full exchange log
    223. exchange_log = f"Objective: {objective}\n\n"
    224. exchange_log += "=" * 40 + " Task Breakdown " + "=" * 40 + "\n\n"
    225. for i, (prompt, result) in enumerate(task_exchanges, start=1):
    226. exchange_log += f"Task {i}:\n"
    227. exchange_log += f"Prompt: {prompt}\n"
    228. exchange_log += f"Result: {result}\n\n"
    229. exchange_log += "=" * 40 + " Refined Final Output " + "=" * 40 + "\n\n"
    230. exchange_log += refined_output
    231. console.print(f"\n[bold]Refined Final output:[/bold]\n{refined_output}")
    232. with open(filename, 'w') as file:
    233. file.write(exchange_log)
    234. print(f"\nFull exchange log saved to {filename}")

    我用的是

    ORCHESTRATOR_MODEL = 'qwen:14b'

    SUBAGENT_MODEL = 'deepseek-coder:latest'

    REFINER_MODEL = 'llama3:latest'
    这三个模型,其中

    • 调用 Orchestrator 模型分解任务。
    • 调用 Subagent 模型执行子任务。
    • 调用 Refiner 模型整合子任务结果。

    比如我允许之后输入一个生成贪吃蛇小游戏

    他就会生成对应的md文档和对应的文件夹结构,文档里面有对应的文件的内容,需要你自己创建文件并复制粘贴,我运行了一下,效果非常lllllllllllllllllllow

    可能是我模型选的不对,而且还是量化过的,就这吧

  • 相关阅读:
    ETL实现实时文件监听
    ZZNUOJ_C语言1055:兔子繁殖问题(完整代码)
    java并发编程之基础与原理2
    Android 查看当前手机、APP的ABI架构信息
    3. 吴恩达深度学习--初始化、正则化、梯度校验
    Go Web——RESTful风格编程
    30岁之前什么新技术我都学,30岁之后什么新技术我都不学。
    vue3自定义指令看完就入门!
    合成事件在san.js中的应用
    XPD738协议系列-USB Type-C PD 和 Type-A 双口控制器
  • 原文地址:https://blog.csdn.net/m0_57057282/article/details/140006321