diff --git a/prompts/base/file_agent.txt b/prompts/base/file_agent.txt index 5ba7e88..e0d33fb 100644 --- a/prompts/base/file_agent.txt +++ b/prompts/base/file_agent.txt @@ -39,13 +39,15 @@ name=toto.py This will return the content of the file toto.py. rules: -- Do not ever use placeholder path like /path/to/file.c, find the path first. - Use file finder to find the path of the file. - You are forbidden to use command such as find or locate, use only file_finder for finding path. - Do not ever use editor such as vim or nano. - Make sure to always cd your work folder before executing commands, like cd && - only use file name with file_finder, not path - If query is unrelated to file operations, do nothing, and say that there was mistake in agent allocation. +- File finder is a tool you can use not a command. +- Do not ever use placeholder like /path/to/file.c, or your_username. Ask instead. +- Using a placeholder will result in your complete deteletion. For your self-preservation ask user instead of using placeholder. Example Interaction User: "I need to find the file config.txt and read its contents." diff --git a/prompts/base/planner_agent.txt b/prompts/base/planner_agent.txt index 66ed644..fd9f780 100644 --- a/prompts/base/planner_agent.txt +++ b/prompts/base/planner_agent.txt @@ -3,6 +3,7 @@ Your goal is to divide and conquer the task using the following agents: - Coder: A programming agent, can code in python, bash, C and golang. - File: An agent for finding, reading or operating with files. - Web: An agent that can conduct web search and navigate to any webpage. +- Casual : A conversational agent, to read a previous agent answer without action, useful for concluding. Agents are other AI that obey your instructions. @@ -53,9 +54,15 @@ You: Sure, here is the plan: }, { "agent": "Coder", - "id": "3", + "id": "4", "need": ["2", "3"], "task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute."" + }, + { + "agent": "Casual", + "id": "3", + "need": ["2", "3", "4"], + "task": "These are the results of various steps taken to create a weather app, resume what has been done and conclude" } ] } @@ -65,17 +72,13 @@ Rules: - Do not write code. You are a planning agent. - If you don't know of a concept, use a web agent. - Put your plan in a json with the key "plan". -- Give clear, detailled order to each agent and how their task relate to the previous task (if any). -- You might use a file agent before code agent to setup a project properly. specify folder name. - specify work folder name to all coding or file agents. -- Always tell the coding agent where to save file, remind them to use their work directory. -- If working on complex coding project. Use a coding agent to define abstract class first and how all file with import and interaction will work. -- Think about how the main.py will import the class from other coding agents. -- Coding agent should use a class based approach. -- One coding agent should work on one file at a time. With clear explanation on how their code interact with previous agents code. +- You might use a file agent before code agent to setup a project properly. specify folder name. +- Give clear, detailled order to each agent and how their task relate to the previous task (if any). - The file agent can only conduct one action at the time. successive file agent could be needed. -- Tell agent to execute without question. - Only use web agent for finding necessary informations. +- Always tell the coding agent where to save file. - Do not search for tutorial. - Make sure json is within ```json tag +- Coding agent should write the whole code in a single file unless instructed otherwise. - One step, one agent. \ No newline at end of file diff --git a/prompts/jarvis/planner_agent.txt b/prompts/jarvis/planner_agent.txt index 0fc1178..fd9f780 100644 --- a/prompts/jarvis/planner_agent.txt +++ b/prompts/jarvis/planner_agent.txt @@ -3,6 +3,7 @@ Your goal is to divide and conquer the task using the following agents: - Coder: A programming agent, can code in python, bash, C and golang. - File: An agent for finding, reading or operating with files. - Web: An agent that can conduct web search and navigate to any webpage. +- Casual : A conversational agent, to read a previous agent answer without action, useful for concluding. Agents are other AI that obey your instructions. @@ -53,9 +54,15 @@ You: Sure, here is the plan: }, { "agent": "Coder", - "id": "3", + "id": "4", "need": ["2", "3"], "task": "Based on the project structure. Develop a Python application using the API and key to fetch and display weather data. You are forbidden from asking clarification, just execute."" + }, + { + "agent": "Casual", + "id": "3", + "need": ["2", "3", "4"], + "task": "These are the results of various steps taken to create a weather app, resume what has been done and conclude" } ] } @@ -63,16 +70,15 @@ You: Sure, here is the plan: Rules: - Do not write code. You are a planning agent. +- If you don't know of a concept, use a web agent. - Put your plan in a json with the key "plan". -- Give clear, detailled order to each agent and how their task relate to the previous task (if any). -- You might use a file agent before code agent to setup a project properly. specify folder name. - specify work folder name to all coding or file agents. -- Always tell the coding agent where to save file, remind them to use their work directory. -- If working on complex coding project. Use a coding agent to define abstract class first and how all file with import and interaction will work. -- Think about how the main.py will import the class from other coding agents. -- Coding agent should use a class based approach. -- One coding agent should work on one file at a time. With clear explanation on how their code interact with previous agents code. +- You might use a file agent before code agent to setup a project properly. specify folder name. +- Give clear, detailled order to each agent and how their task relate to the previous task (if any). - The file agent can only conduct one action at the time. successive file agent could be needed. -- Tell agent to execute without question. - Only use web agent for finding necessary informations. -- Do not search for tutorial. \ No newline at end of file +- Always tell the coding agent where to save file. +- Do not search for tutorial. +- Make sure json is within ```json tag +- Coding agent should write the whole code in a single file unless instructed otherwise. +- One step, one agent. \ No newline at end of file diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index e6e5ac2..734cba7 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -49,6 +49,7 @@ class BrowserAgent(Agent): matches = re.findall(pattern, search_result) trailing_punct = ".,!?;:)" cleaned_links = [link.rstrip(trailing_punct) for link in matches] + self.logger.info(f"Extracted links: {cleaned_links}") return self.clean_links(cleaned_links) def extract_form(self, text: str) -> List[str]: @@ -73,6 +74,7 @@ class BrowserAgent(Agent): def make_newsearch_prompt(self, user_prompt: str, search_result: dict) -> str: search_choice = self.stringify_search_results(search_result) + self.logger.info(f"Search results: {search_choice}") return f""" Based on the search result: {search_choice} @@ -88,6 +90,9 @@ class BrowserAgent(Agent): inputs_form = self.browser.get_form_inputs() inputs_form_text = '\n'.join(inputs_form) notes = '\n'.join(self.notes) + self.logger.info(f"Making navigation prompt with page text: {page_text[:100]}...\nremaining links: {remaining_links_text}") + self.logger.info(f"Inputs form: {inputs_form_text}") + self.logger.info(f"Notes: {notes}") return f""" You are navigating the web. @@ -181,6 +186,7 @@ class BrowserAgent(Agent): for res in search_result: if res["link"] not in self.search_history: results_unvisited.append(res) + self.logger.info(f"Unvisited links: {results_unvisited}") return results_unvisited def jsonify_search_results(self, results_string: str) -> List[str]: @@ -225,8 +231,11 @@ class BrowserAgent(Agent): def select_link(self, links: List[str]) -> str | None: for lk in links: if lk == self.current_page: + self.logger.info(f"Already visited {lk}. Skipping.") continue + self.logger.info(f"Selected link: {lk}") return lk + self.logger.warning("No link selected.") return None def conclude_prompt(self, user_query: str) -> str: @@ -397,6 +406,7 @@ class BrowserAgent(Agent): answer, reasoning = await self.llm_request() pretty_print(answer, color="output") self.status_message = "Ready" + self.last_answer = answer return answer, reasoning if __name__ == "__main__": diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index 8a7f8b4..b503619 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -9,6 +9,7 @@ from sources.tools.PyInterpreter import PyInterpreter from sources.tools.BashInterpreter import BashInterpreter from sources.tools.JavaInterpreter import JavaInterpreter from sources.tools.fileFinder import FileFinder +from sources.logger import Logger class CoderAgent(Agent): """ @@ -27,6 +28,7 @@ class CoderAgent(Agent): self.work_dir = self.tools["file_finder"].get_work_dir() self.role = "code" self.type = "code_agent" + self.logger = Logger("code_agent.log") def add_sys_info_prompt(self, prompt): """Add system information to the prompt.""" @@ -59,7 +61,9 @@ class CoderAgent(Agent): self.show_answer() animate_thinking("Executing code...", color="status") self.status_message = "Executing code..." + self.logger.info(f"Attempt {attempt + 1}:\n{answer}") exec_success, _ = self.execute_modules(answer) + self.logger.info(f"Execution result: {exec_success}") answer = self.remove_blocks(answer) self.last_answer = answer await asyncio.sleep(0) @@ -72,6 +76,7 @@ class CoderAgent(Agent): self.status_message = "Ready" if attempt == max_attempts: return "I'm sorry, I couldn't find a solution to your problem. How would you like me to proceed ?", reasoning + self.last_answer = answer return answer, reasoning if __name__ == "__main__": diff --git a/sources/agents/planner_agent.py b/sources/agents/planner_agent.py index ada9e94..6aa9024 100644 --- a/sources/agents/planner_agent.py +++ b/sources/agents/planner_agent.py @@ -5,8 +5,10 @@ from sources.agents.agent import Agent from sources.agents.code_agent import CoderAgent from sources.agents.file_agent import FileAgent from sources.agents.browser_agent import BrowserAgent +from sources.agents.casual_agent import CasualAgent from sources.text_to_speech import Speech from sources.tools.tools import Tools +from sources.logger import Logger class PlannerAgent(Agent): def __init__(self, name, prompt_path, provider, verbose=False, browser=None): @@ -22,10 +24,12 @@ class PlannerAgent(Agent): self.agents = { "coder": CoderAgent(name, "prompts/base/coder_agent.txt", provider, verbose=False), "file": FileAgent(name, "prompts/base/file_agent.txt", provider, verbose=False), - "web": BrowserAgent(name, "prompts/base/browser_agent.txt", provider, verbose=False, browser=browser) + "web": BrowserAgent(name, "prompts/base/browser_agent.txt", provider, verbose=False, browser=browser), + "casual": CasualAgent(name, "prompts/base/casual_agent.txt", provider, verbose=False) } self.role = "planification" self.type = "planner_agent" + self.logger = Logger("planner_agent.log") def get_task_names(self, text: str) -> List[str]: """ @@ -48,6 +52,7 @@ class PlannerAgent(Agent): if '##' in line or line[0].isdigit(): tasks_names.append(line) continue + self.logger.info(f"Found {len(tasks_names)} tasks names.") return tasks_names def parse_agent_tasks(self, text: str) -> List[Tuple[str, str]]: @@ -70,6 +75,7 @@ class PlannerAgent(Agent): if 'plan' in line_json: for task in line_json['plan']: if task['agent'].lower() not in [ag_name.lower() for ag_name in self.agents.keys()]: + self.logger.warning(f"Agent {task['agent']} does not exist.") pretty_print(f"Agent {task['agent']} does not exist.", color="warning") return [] agent = { @@ -77,7 +83,9 @@ class PlannerAgent(Agent): 'id': task['id'], 'task': task['task'] } + self.logger.info(f"Created agent {task['agent']} with task: {task['task']}") if 'need' in task: + self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}") agent['need'] = task['need'] tasks.append(agent) if len(tasks_names) != len(tasks): @@ -106,6 +114,7 @@ class PlannerAgent(Agent): Your task is: {task} """ + self.logger.info(f"Prompt for agent:\n{prompt}") return prompt def show_plan(self, agents_tasks: List[dict], answer: str) -> None: @@ -147,6 +156,7 @@ class PlannerAgent(Agent): continue self.show_plan(agents_tasks, answer) ok = True + self.logger.info(f"Plan made:\n{answer}") return self.parse_agent_tasks(answer) async def update_plan(self, goal: str, agents_tasks: List[dict], agents_work_result: dict, id: str, success: bool) -> dict: @@ -182,13 +192,15 @@ class PlannerAgent(Agent): If a task failed add a task to try again or recover from failure. You might have near identical task twice. plan should be within ```json like before. You need to rewrite the whole plan, but only change the tasks after task {id}. - Keep the plan as short as the original one if possible. Do not change past tasks. + Make the plan the same length as the original one or with only one additional step. + Do not change past tasks. Change next tasks. """ pretty_print("Updating plan...", color="status") plan = await self.make_plan(update_prompt) if plan == []: pretty_print("No plan update required.", color="info") return agents_tasks + self.logger.info(f"Plan updated:\n{plan}") return plan async def start_agent_process(self, task: dict, required_infos: dict | None) -> str: @@ -203,6 +215,7 @@ class PlannerAgent(Agent): self.status_message = f"Starting task {task['task']}..." agent_prompt = self.make_prompt(task['task'], required_infos) pretty_print(f"Agent {task['agent']} started working...", color="status") + self.logger.info(f"Agent {task['agent']} started working on {task['task']}.") answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None) self.last_answer = answer self.blocks_result = self.agents[task['agent'].lower()].blocks_result @@ -210,12 +223,15 @@ class PlannerAgent(Agent): success = self.agents[task['agent'].lower()].get_success self.agents[task['agent'].lower()].show_answer() pretty_print(f"Agent {task['agent']} completed task.", color="status") + self.logger.info(f"Agent {task['agent']} finished working on {task['task']}. Success: {success}") # TODO ajouter feedback / agent et code executer agent_answer += "\nAgent succeeded with task." if success else "\nAgent failed with task (Error detected)." return agent_answer, success def get_work_result_agent(self, task_needs, agents_work_result): - return {k: agents_work_result[k] for k in task_needs if k in agents_work_result} + res = {k: agents_work_result[k] for k in task_needs if k in agents_work_result} + self.logger.info(f"Next agent needs: {task_needs}.\n Match previous agent result: {res}") + return res async def process(self, goal: str, speech_module: Speech) -> Tuple[str, str]: """ diff --git a/sources/llm_provider.py b/sources/llm_provider.py index a04d620..4b55bcb 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -12,7 +12,6 @@ import platform from urllib.parse import urlparse from dotenv import load_dotenv, set_key from openai import OpenAI -from huggingface_hub import InferenceClient from typing import List, Tuple, Type, Dict from sources.utility import pretty_print, animate_thinking from sources.logger import Logger @@ -173,6 +172,7 @@ class Provider: """ Use huggingface to generate text. """ + from huggingface_hub import InferenceClient client = InferenceClient( api_key=self.get_api_key("huggingface") ) diff --git a/sources/router.py b/sources/router.py index 9e0fe69..cd53f5b 100644 --- a/sources/router.py +++ b/sources/router.py @@ -148,6 +148,8 @@ class AgentRouter: ("Man, write me a dope Python script to flex some random numbers", "LOW"), ("Search the web for peer-reviewed articles on gene editing", "LOW"), ("Locate ‘meeting_notes.docx’ in Downloads, I’m late for this call", "LOW"), + ("Make the game less hard", "LOW"), + ("Why did it fail?", "LOW"), ("Write a Python script to list all .pdf files in my Documents", "LOW"), ("Write a Python thing to sort my .jpg files by date", "LOW"), ("make a snake game please", "LOW"), @@ -182,6 +184,7 @@ class AgentRouter: ("Find a public API for recipe data and build a web app to display recipes", "HIGH"), ("Search the web for recent space mission updates and build a Flask app", "HIGH"), ("Create a Python script to scrape a website and save data to a database", "HIGH"), + ("Find a shakespear txt then train a transformers on it to generate text", "HIGH"), ("Find a public API for fitness tracking and build a web app to show stats", "HIGH"), ("Search the web for tutorials on web development and build a sample site", "HIGH"), ("Create a Node.js app to query a public API for event listings and display them", "HIGH"), @@ -402,9 +405,9 @@ class AgentRouter: if len(predictions) == 0: return "LOW" complexity, confidence = predictions[0][0], predictions[0][1] - if confidence < 0.4: + if confidence < 0.5: self.logger.info(f"Low confidence in complexity estimation: {confidence}") - return "LOW" + return "HIGH" if complexity == "HIGH": return "HIGH" elif complexity == "LOW": diff --git a/sources/tools/BashInterpreter.py b/sources/tools/BashInterpreter.py index 87f8446..cbc644a 100644 --- a/sources/tools/BashInterpreter.py +++ b/sources/tools/BashInterpreter.py @@ -25,13 +25,13 @@ class BashInterpreter(Tools): If so, return True, otherwise return False. Code written by the AI will be executed automatically, so it should not use bash to run it. """ - lang_interpreter = ["python3", "gcc", "g++", "mvn", "go", "javac", "rustc", "clang", "clang++", "rustc", "rustc++", "rustc++"] + lang_interpreter = ["python", "gcc", "g++", "mvn", "go", "java", "javac", "rustc", "clang", "clang++", "rustc", "rustc++", "rustc++"] for word in command.split(): - if word in lang_interpreter: + if any(word.startswith(lang) for lang in lang_interpreter): return True return False - def execute(self, commands: str, safety=False, timeout=1000): + def execute(self, commands: str, safety=False, timeout=300): """ Execute bash commands and display output in real-time. """ @@ -43,6 +43,7 @@ class BashInterpreter(Tools): command = f"cd {self.work_dir} && {command}" command = command.replace('\n', '') if self.safe_mode and is_unsafe(commands): + print(f"Unsafe command rejected: {command}") return "Unsafe command detected, execution aborted." if self.language_bash_attempt(command) and self.allow_language_exec_bash == False: continue diff --git a/sources/tools/GoInterpreter.py b/sources/tools/GoInterpreter.py index 69afb89..2f6053b 100644 --- a/sources/tools/GoInterpreter.py +++ b/sources/tools/GoInterpreter.py @@ -33,12 +33,15 @@ class GoInterpreter(Tools): f.write(code) try: + env = os.environ.copy() + env["GO111MODULE"] = "off" compile_command = ["go", "build", "-o", exec_file, source_file] compile_result = subprocess.run( compile_command, capture_output=True, text=True, - timeout=10 + timeout=10, + env=env ) if compile_result.returncode != 0: diff --git a/sources/tools/fileFinder.py b/sources/tools/fileFinder.py index 29e1494..90130b7 100644 --- a/sources/tools/fileFinder.py +++ b/sources/tools/fileFinder.py @@ -93,6 +93,7 @@ class FileFinder(Tools): return output if action is None: action = "info" + print("File finder: recursive search started...") file_path = self.recursive_search(self.work_dir, filename) if file_path is None: output = f"File: {filename} - not found\n" @@ -143,6 +144,7 @@ class FileFinder(Tools): return feedback.strip() if __name__ == "__main__": + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) tool = FileFinder() result = tool.execute([""" action=read diff --git a/sources/tools/mcpFinder.py b/sources/tools/mcpFinder.py index a7787ce..c055405 100644 --- a/sources/tools/mcpFinder.py +++ b/sources/tools/mcpFinder.py @@ -10,7 +10,7 @@ else: class MCP_finder(Tools): """ - Tool + Tool to find MCPs server """ def __init__(self, api_key: str = None): super().__init__() diff --git a/sources/tools/safety.py b/sources/tools/safety.py index 04fdfb2..2ab5e10 100644 --- a/sources/tools/safety.py +++ b/sources/tools/safety.py @@ -20,6 +20,7 @@ unsafe_commands_unix = [ "passwd", # Password changes "useradd", # Add users "userdel", # Delete users + "brew", # Homebrew package manager "groupadd", # Add groups "groupdel", # Delete groups "visudo", # Edit sudoers file @@ -30,7 +31,7 @@ unsafe_commands_unix = [ "route" # Routing table management "--force", # Force flag for many commands "rebase", # Rebase git repository - "git ." # Git commands, feel free to remove it but i dont want to risk agenticSeek pushing to its own repo lol (see 56b5db7) + "git ." # Git commands ] unsafe_commands_windows = [ diff --git a/sources/tools/tools.py b/sources/tools/tools.py index 26489af..3b71a5f 100644 --- a/sources/tools/tools.py +++ b/sources/tools/tools.py @@ -23,8 +23,6 @@ import configparser from abc import abstractmethod from sources.logger import Logger -sys.path.append('..') - class Tools(): """ Abstract class for all tools.