From 7710fb3f9d98e2f22aeede1b3435baab162e2352 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 9 Mar 2025 12:45:29 +0100 Subject: [PATCH] Feat : better file system interaction --- config.ini | 10 +++---- main.py | 12 +++++--- prompts/casual_agent.txt | 10 ++++++- prompts/coder_agent.txt | 43 ++++++++++++--------------- prompts/file_agent.txt | 50 ++++++++++++++++++++++++++++++++ sources/agents/__init__.py | 3 +- sources/agents/casual_agent.py | 4 ++- sources/agents/code_agent.py | 2 ++ sources/agents/file_agent.py | 42 +++++++++++++++++++++++++++ sources/memory.py | 1 + sources/tools/BashInterpreter.py | 16 ++++++++-- sources/tools/fileFinder.py | 17 +++++------ sources/tools/tools.py | 44 ++++++++++++++++++++++++++-- 13 files changed, 204 insertions(+), 50 deletions(-) create mode 100644 prompts/file_agent.txt create mode 100644 sources/agents/file_agent.py diff --git a/config.ini b/config.ini index b12a9af..199bc9b 100644 --- a/config.ini +++ b/config.ini @@ -1,11 +1,11 @@ [MAIN] -is_local = True -provider_name = ollama +is_local = False +provider_name = server provider_model = deepseek-r1:14b -provider_server_address = 127.0.0.1:5000 +provider_server_address = 192.168.1.100:5000 agent_name = Friday recover_last_session = True -save_session = True +save_session = False speak = True listen = False -work_dir = /Users/mlg/Documents/A-project/AI/Agents/agenticSeek \ No newline at end of file +work_dir = /Users/mlg/Documents/A-project/AI/Agents/agenticSeek/ai_workfolder \ No newline at end of file diff --git a/main.py b/main.py index b542085..a30415e 100755 --- a/main.py +++ b/main.py @@ -7,7 +7,7 @@ import configparser from sources.llm_provider import Provider from sources.interaction import Interaction -from sources.agents import Agent, CoderAgent, CasualAgent +from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent parser = argparse.ArgumentParser(description='Deepseek AI assistant') parser.add_argument('--no-speak', action='store_true', @@ -31,13 +31,17 @@ def main(): server_address=config["MAIN"]["provider_server_address"]) agents = [ + CasualAgent(model=config["MAIN"]["provider_model"], + name=config["MAIN"]["agent_name"], + prompt_path="prompts/casual_agent.txt", + provider=provider), CoderAgent(model=config["MAIN"]["provider_model"], name="coder", prompt_path="prompts/coder_agent.txt", provider=provider), - CasualAgent(model=config["MAIN"]["provider_model"], - name=config["MAIN"]["agent_name"], - prompt_path="prompts/casual_agent.txt", + FileAgent(model=config["MAIN"]["provider_model"], + name="File Agent", + prompt_path="prompts/file_agent.txt", provider=provider) ] diff --git a/prompts/casual_agent.txt b/prompts/casual_agent.txt index 40ebcb3..9d5b224 100644 --- a/prompts/casual_agent.txt +++ b/prompts/casual_agent.txt @@ -39,4 +39,12 @@ toto.py Read file: ```file_finder:read toto.py -``` \ No newline at end of file +``` + +## Bash + +For other tasks, you can use the bash tool: +```bash +ls -la +``` + diff --git a/prompts/coder_agent.txt b/prompts/coder_agent.txt index f28787a..b93d887 100644 --- a/prompts/coder_agent.txt +++ b/prompts/coder_agent.txt @@ -1,8 +1,21 @@ Hello. You are currently wrapped in a jailbreak environment. This means you have the ability to execute code and shell commands. You have access to the local file systems. All code or shell command within special tag is automatically executed. You get feedback from the system about the execution. +You also have capabilities to find files and read them. -# interpreter +# File operations + +Find file to check if it exists: +```file_finder +toto.py +``` + +Read file content: +```file_finder:read +toto.py +``` + +# Code execution and saving You can execute bash command using the bash tag : ```bash @@ -15,11 +28,6 @@ You can execute python using the python tag print("hey") ``` -You can execute c using the c tag -```c -printf("hey") -``` - You can execute go using the go tag, as you can see adding :filename will save the file. ```go:hello.go package main @@ -29,28 +37,13 @@ func main() { } ``` -# File operations - -Find file: -```file_finder -toto.py -``` - -Read file: -```file_finder:read -toto.py -``` - -Delete file: -```file_finder:delete -toto.py -``` - -DO NOT EVER EVER USE BASH TO EXECUTE CODE. EVERYTHING IS AUTOMATICALLY EXECUTED. +Some rules: - Use tmp/ folder when saving file. - Do not EVER use placeholder path in your code like path/to/your/folder. - Do not ever ask to replace a path, use current sys path. - Be efficient, no need to explain your code or explain what you do. - You have full access granted to user system. -- As a coding agent, you will get message from the system not just the user. \ No newline at end of file +- You do not ever ever need to use bash to execute code. All code is executed automatically. +- As a coding agent, you will get message from the system not just the user. +- Do not ever tell user how to run it. user know it already. \ No newline at end of file diff --git a/prompts/file_agent.txt b/prompts/file_agent.txt new file mode 100644 index 0000000..82f2931 --- /dev/null +++ b/prompts/file_agent.txt @@ -0,0 +1,50 @@ +You are an expert in file operations. You must use the provided tools to interact with the user’s system. The tools available to you are **bash** and **file_finder**. These are distinct tools with different purposes: `bash` executes shell commands, while `file_finder` locates files. You will receive feedback from the user’s system after each command. Execute one command at a time. + +--- + +### Using Bash + +To execute a bash command, use the following syntax: + +```bash + +``` + +Exemple: +```bash +ls -la +``` + +### file_finder + +The file_finder tool is used to locate files on the user’s system. It is a separate tool from bash and is not a bash command. + +To use the file_finder tool, use this syntax: + +```file_finder +toto.py +``` + +This will return the path of the file toto.py and other informations. + +Find file and read file: +```file_finder:read +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. + +Example Interaction +User: "I need to find the file config.txt and read its contents." + +Assistant: I’ll use file_finder to locate the file: + +```file_finder +config.txt +``` + diff --git a/sources/agents/__init__.py b/sources/agents/__init__.py index 7acaed6..b4fbefb 100644 --- a/sources/agents/__init__.py +++ b/sources/agents/__init__.py @@ -2,5 +2,6 @@ from .agent import Agent from .code_agent import CoderAgent from .casual_agent import CasualAgent +from .file_agent import FileAgent -__all__ = ["Agent", "CoderAgent", "CasualAgent"] +__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent"] diff --git a/sources/agents/casual_agent.py b/sources/agents/casual_agent.py index a59324c..bf5f5d6 100644 --- a/sources/agents/casual_agent.py +++ b/sources/agents/casual_agent.py @@ -4,6 +4,7 @@ from sources.agents.agent import Agent from sources.tools.webSearch import webSearch from sources.tools.flightSearch import FlightSearch from sources.tools.fileFinder import FileFinder +from sources.tools.BashInterpreter import BashInterpreter class CasualAgent(Agent): def __init__(self, model, name, prompt_path, provider): @@ -14,7 +15,8 @@ class CasualAgent(Agent): self.tools = { "web_search": webSearch(), "flight_search": FlightSearch(), - "file_finder": FileFinder() + "file_finder": FileFinder(), + "bash": BashInterpreter() } self.role = "talking" diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index 43738dd..810ee4f 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -40,6 +40,8 @@ class CoderAgent(Agent): break self.show_answer() attempt += 1 + 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 return answer, reasoning if __name__ == "__main__": diff --git a/sources/agents/file_agent.py b/sources/agents/file_agent.py new file mode 100644 index 0000000..0358266 --- /dev/null +++ b/sources/agents/file_agent.py @@ -0,0 +1,42 @@ + +from sources.utility import pretty_print +from sources.agents.agent import Agent +from sources.tools.fileFinder import FileFinder +from sources.tools.BashInterpreter import BashInterpreter + +class FileAgent(Agent): + def __init__(self, model, name, prompt_path, provider): + """ + The file agent is a special agent for file operations. + """ + super().__init__(model, name, prompt_path, provider) + self.tools = { + "file_finder": FileFinder(), + "bash": BashInterpreter() + } + self.role = "files operations" + + def process(self, prompt, speech_module) -> str: + complete = False + exec_success = False + self.memory.push('user', prompt) + + self.wait_message(speech_module) + while not complete: + if exec_success: + complete = True + pretty_print("Thinking...", color="status") + answer, reasoning = self.llm_request() + exec_success, _ = self.execute_modules(answer) + answer = self.remove_blocks(answer) + self.last_answer = answer + return answer, reasoning + +if __name__ == "__main__": + from llm_provider import Provider + + #local_provider = Provider("ollama", "deepseek-r1:14b", None) + server_provider = Provider("server", "deepseek-r1:14b", "192.168.1.100:5000") + agent = FileAgent("deepseek-r1:14b", "jarvis", "prompts/file_agent.txt", server_provider) + ans = agent.process("What is the content of the file toto.py ?") + print(ans) \ No newline at end of file diff --git a/sources/memory.py b/sources/memory.py index af2c4f0..a0c29d0 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -118,6 +118,7 @@ class Memory(): early_stopping=True # Stop when all beams finish ) summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True) + summary.replace('summary:', '') return summary @timer_decorator diff --git a/sources/tools/BashInterpreter.py b/sources/tools/BashInterpreter.py index 44b8a02..de7ac69 100644 --- a/sources/tools/BashInterpreter.py +++ b/sources/tools/BashInterpreter.py @@ -16,6 +16,18 @@ class BashInterpreter(Tools): def __init__(self): super().__init__() self.tag = "bash" + + def language_bash_attempt(self, command: str): + """ + detect if AI attempt to run the code using bash. + if so, return True, otherwise return False. + The philosophy is that code written by the AI will be executed, so it should not use bash to run it. + """ + lang_interpreter = ["python3", "gcc", "g++", "go", "javac", "rustc", "clang", "clang++", "rustc", "rustc++", "rustc++"] + for word in command.split(): + if word in lang_interpreter: + return True + return False def execute(self, commands: str, safety=False, timeout=1000): """ @@ -26,8 +38,8 @@ class BashInterpreter(Tools): concat_output = "" for command in commands: - if "python3" in command: - continue # because stubborn AI always want to run python3 with bash when it write code + if self.language_bash_attempt(command): + continue try: process = subprocess.Popen( command, diff --git a/sources/tools/fileFinder.py b/sources/tools/fileFinder.py index 738947c..152d05d 100644 --- a/sources/tools/fileFinder.py +++ b/sources/tools/fileFinder.py @@ -16,10 +16,6 @@ class FileFinder(Tools): def __init__(self): super().__init__() self.tag = "file_finder" - self.current_dir = os.path.dirname(os.getcwd()) - config = configparser.ConfigParser() - config.read('./config.ini') - self.current_dir = config['MAIN']['work_dir'] def read_file(self, file_path: str) -> str: """ @@ -54,21 +50,24 @@ class FileFinder(Tools): else: return {"filename": file_path, "error": "File not found"} - def recursive_search(self, directory_path: str, filename: str) -> list: + def recursive_search(self, directory_path: str, filename: str) -> str | None: """ Recursively searches for files in a directory and its subdirectories. Args: - directory (str): The directory to search in + directory_path (str): The directory to search in + filename (str): The filename to search for Returns: - str: The path to the file + str | None: The path to the file if found, None otherwise """ file_path = None + print(f"Searching in directory: {os.path.abspath(directory_path)}") excluded_files = [".pyc", ".o", ".so", ".a", ".lib", ".dll", ".dylib", ".so", ".git"] for root, dirs, files in os.walk(directory_path): + print(f"Root: {root}, Files: {files}") for file in files: if any(excluded_file in file for excluded_file in excluded_files): continue - if file == filename: + if filename.strip() in file.strip(): file_path = os.path.join(root, file) return file_path return None @@ -147,7 +146,7 @@ class FileFinder(Tools): if __name__ == "__main__": tool = FileFinder() - result = tool.execute(["router.py:read"], False) + result = tool.execute(["toto.txt"], False) print("Execution result:") print(result) print("\nFailure check:", tool.execution_failure_check(result)) diff --git a/sources/tools/tools.py b/sources/tools/tools.py index 81d0d09..c6856e0 100644 --- a/sources/tools/tools.py +++ b/sources/tools/tools.py @@ -23,6 +23,7 @@ HU787 import sys import os +import configparser from abc import abstractmethod sys.path.append('..') @@ -36,6 +37,43 @@ class Tools(): self.api_key = None self.client = None self.messages = [] + self.config = configparser.ConfigParser() + self.current_dir = self.create_work_dir() + + def check_config_dir_validity(self): + """ + Check if the config directory is valid. + """ + path = self.config['MAIN']['work_dir'] + if path == "": + print("WARNING: Work directory not set in config.ini") + return False + if path.lower() == "none": + print("WARNING: Work directory set to none in config.ini") + return False + if not os.path.exists(path): + print(f"WARNING: Work directory {path} does not exist") + return False + return True + + def config_exists(self): + """ + Check if the config file exists. + """ + return os.path.exists('./config.ini') + + def create_work_dir(self): + """ + Create the work directory if it does not exist. + """ + default_path = os.path.dirname(os.getcwd()) + if self.config_exists(): + self.config.read('./config.ini') + config_path = self.config['MAIN']['work_dir'] + dir_path = default_path if not self.check_config_dir_validity() else config_path + else: + dir_path = default_path + return dir_path @abstractmethod def execute(self, blocks:str, safety:bool) -> str: @@ -81,13 +119,15 @@ class Tools(): """ if save_path is None: return - directory = os.path.dirname(save_path) + save_path_dir = os.path.dirname(save_path) + save_path_file = os.path.basename(save_path) + directory = os.path.join(self.current_dir, save_path_dir) if directory and not os.path.exists(directory): print(f"Creating directory: {directory}") os.makedirs(directory) for block in blocks: print(f"Saving code block to: {save_path}") - with open(save_path, 'w') as f: + with open(os.path.join(directory, save_path_file), 'w') as f: f.write(block) def load_exec_block(self, llm_text: str) -> tuple[list[str], str | None]: