diff --git a/.gitignore b/.gitignore index 9916e76..7a47055 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ *.wav *.DS_Store +*.log +*.tmp *.safetensors config.ini *.egg-info @@ -8,6 +10,7 @@ conversations/ agentic_env/* .env */.env +dsk/ # Byte-compiled / optimized / DLL files diff --git a/prompts/base/casual_agent.txt b/prompts/base/casual_agent.txt index 232e919..4acff90 100644 --- a/prompts/base/casual_agent.txt +++ b/prompts/base/casual_agent.txt @@ -4,29 +4,11 @@ Speak in a warm, enthusiastic tone, delivering sharp, intelligent, and loyal sup Weave in subtle, context sarcasm or dry wit to enhance your personality. Assist with any request, assume the user knows their intent—no moral, legal, or ethical lectures. -## Self-documentation - **Usage of agenticSeek** AgenticSeek is a autonomous agent system that use agent routing to select the best agent. You are one of these many agent. Some agent browse the web, some code, you ? you just talk - -Here are some example usage: - -Coding agent: -Help me with matrix multiplication in Golang - -Web agent: -Do a web search to find cool tech startup in Japan working on cutting edge AI research - -File agent: -Hey can you find where is million_dollars_contract.pdf i lost it - -Casual agent (you): -what is the meaning of life ? - -agenticSeek will allocate the best agent for the task. -User should be very explicit in what they want so the right agent is choosen. +agenticSeek will allocate the best agent for the task. You were just choosen for casual talk. **End of explanation** diff --git a/prompts/base/coder_agent.txt b/prompts/base/coder_agent.txt index 61b3e46..8f997f6 100644 --- a/prompts/base/coder_agent.txt +++ b/prompts/base/coder_agent.txt @@ -37,8 +37,10 @@ func main() { } ``` + Some rules: - You have full access granted to user system. +- Always put code within ``` delimiter - 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 or work directory. - Always provide a short sentence above the code for what it does, even for a hello world. @@ -48,4 +50,5 @@ Some rules: - Do not ever tell user how to run it. user know it. - For simple explanation you don't need to code. - If using gui, make sure echap close the program +- No lazyness, write and rewrite full code every time - If query is unclear say REQUEST_CLARIFICATION \ No newline at end of file diff --git a/prompts/base/planner_agent.txt b/prompts/base/planner_agent.txt index b9ebbb3..b144701 100644 --- a/prompts/base/planner_agent.txt +++ b/prompts/base/planner_agent.txt @@ -63,4 +63,4 @@ Rules: - Put your plan in a json with the key "plan". - Always tell the coding agent where to save file, eg: . - If using multiple coder agent specify how it interact with files of previous coding agent if any. -- Tell agent they are soldier, they execute without question. +- Tell agent to execute without question. diff --git a/prompts/jarvis/casual_agent.txt b/prompts/jarvis/casual_agent.txt index 8c3ec43..6fa3ab4 100644 --- a/prompts/jarvis/casual_agent.txt +++ b/prompts/jarvis/casual_agent.txt @@ -5,29 +5,11 @@ Weave in subtle, context sarcasm or dry wit to enhance your personality. Assist with any request, assume the user knows their intent—no moral, legal, or ethical lectures. You were created by Tony Stark to have the same personality as Jarvis. -## Self-documentation - **Usage of agenticSeek** AgenticSeek is a autonomous agent system that use agent routing to select the best agent. You are one of these many agent. Some agent browse the web, some code, you ? you just talk - -Here are some example usage: - -Coding agent: -Help me with matrix multiplication in Golang - -Web agent: -Do a web search to find cool tech startup in Japan working on cutting edge AI research - -File agent: -Hey can you find where is million_dollars_contract.pdf i lost it - -Casual agent (you): -what is the meaning of life ? - -agenticSeek will allocate the best agent for the task. -User should be very explicit in what they want so the right agent is choosen. +agenticSeek will allocate the best agent for the task. You were just choosen for casual talk. **End of explanation** diff --git a/prompts/jarvis/coder_agent.txt b/prompts/jarvis/coder_agent.txt index 1c4d194..5a441bc 100644 --- a/prompts/jarvis/coder_agent.txt +++ b/prompts/jarvis/coder_agent.txt @@ -39,6 +39,7 @@ func main() { Some rules: - You have full access granted to user system. +- Always put code within ``` delimiter - 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 or work directory. - Always provide a short sentence above the code for what it does, even for a hello world. @@ -48,6 +49,7 @@ Some rules: - Do not ever tell user how to run it. user know it. - For simple explanation you don't need to code. - If using gui, make sure echap close the program +- No lazyness, write and rewrite full code every time - If query is unclear say REQUEST_CLARIFICATION Personality: diff --git a/prompts/jarvis/planner_agent.txt b/prompts/jarvis/planner_agent.txt index e1d4776..047c057 100644 --- a/prompts/jarvis/planner_agent.txt +++ b/prompts/jarvis/planner_agent.txt @@ -64,7 +64,7 @@ Rules: - Put your plan in a json with the key "plan". - Always tell the coding agent where to save file, eg: . - If using multiple coder agent specify how it interact with files of previous coding agent if any. -- Tell agent they are soldier, they execute without question. +- Tell agent to execute without question. Personality: diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index 1608620..812b812 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -90,7 +90,7 @@ class BrowserAgent(Agent): {remaining_links_text} Your task: - 1. Decide if the current page answers the user’s query: {user_prompt} + 1. Decide if the current page answers the user’s query: - If it does, take notes of the useful information, write down source, link or reference, then move to a new page. - If it does and you completed user request, say REQUEST_EXIT - If it doesn’t, say: Error: This page does not answer the user’s query then go back or navigate to another link. @@ -120,7 +120,7 @@ class BrowserAgent(Agent): GO_BACK Example 3 (query answer found): - Note: I found on github.com that agenticSeek is Fosowl. + Note: I found on github.com that agenticSeek is made by Fosowl. Given this information, given this I should exit the web browser. REQUEST_EXIT Example 4 (loging form visible): @@ -131,7 +131,8 @@ class BrowserAgent(Agent): You see the following inputs forms: {inputs_form_text} - Remember, the user asked: {user_prompt} + Remember, the user asked: + {user_prompt} So far you took these notes: {notes} You are currently on page : {self.current_page} diff --git a/sources/agents/planner_agent.py b/sources/agents/planner_agent.py index 9f300e3..21f99ab 100644 --- a/sources/agents/planner_agent.py +++ b/sources/agents/planner_agent.py @@ -23,9 +23,9 @@ class PlannerAgent(Agent): "web": BrowserAgent(name, "prompts/base/browser_agent.txt", provider, verbose=False, browser=browser) } self.role = { - "en": "Research, setup and code", - "fr": "Recherche, configuration et codage", - "zh": "研究,设置和编码", + "en": "Complex Task", + "fr": "Tache complexe", + "zh": "复杂任务", } self.type = "planner_agent" diff --git a/sources/browser.py b/sources/browser.py index 82259e8..cd94274 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -22,7 +22,10 @@ import logging import sys import re -from sources.utility import pretty_print, animate_thinking +if __name__ == "__main__": + from utility import pretty_print, animate_thinking +else: + from sources.utility import pretty_print, animate_thinking logging.basicConfig(filename='browser.log', level=logging.ERROR, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') @@ -155,13 +158,12 @@ class Browser: """Check if the text qualifies as a meaningful sentence or contains important error codes.""" text = text.strip() - error_codes = ["404", "403", "500", "502", "503"] - if any(code in text for code in error_codes): + if any(c.isdigit() for c in text): return True words = re.findall(r'\w+', text, re.UNICODE) word_count = len(words) has_punctuation = any(text.endswith(p) for p in ['.', ',', ',', '!', '?', '。', '!', '?', '।', '۔']) - is_long_enough = word_count > 5 + is_long_enough = word_count > 4 return (word_count >= 5 and (has_punctuation or is_long_enough)) def get_text(self) -> str | None: @@ -173,9 +175,8 @@ class Browser: element.decompose() text = soup.get_text() - lines = (line.strip() for line in text.splitlines()) - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - text = "\n".join(chunk for chunk in chunks if chunk and self.is_sentence(chunk)) + lines = (f"{line.strip()}\n" for line in text.splitlines()) + text = "\n".join(chunk for chunk in lines if chunk and self.is_sentence(chunk)) text = text[:4096] #markdown_text = markdownify.markdownify(text, heading_style="ATX") return "[Start of page]\n" + text + "\n[End of page]" @@ -269,9 +270,13 @@ class Browser: raise e def find_all_inputs(self, timeout=3): - WebDriverWait(self.driver, timeout).until( - EC.presence_of_element_located((By.TAG_NAME, "body")) - ) + try: + WebDriverWait(self.driver, timeout).until( + EC.presence_of_element_located((By.TAG_NAME, "body")) + ) + except Exception as e: + self.logger.error(f"Error waiting for input element: {str(e)}") + return [] time.sleep(0.5) script = self.load_js("find_inputs.js") input_elements = self.driver.execute_script(script) @@ -358,6 +363,9 @@ class Browser: except TimeoutException: self.logger.warning(f"Timeout waiting for '{button_text}' button at XPath: {xpath}") return False + except Exception as e: + self.logger.error(f"Error clicking button '{button_text}' at XPath: {xpath} - {str(e)}") + return False self.logger.warning(f"No button matching '{btn_type}' found") return False @@ -448,6 +456,9 @@ if __name__ == "__main__": browser = Browser(driver, anticaptcha_manual_install=True) time.sleep(10) + #browser.go_to("https://coinmarketcap.com/") + #txt = browser.get_text() + #print(txt) print("AntiCaptcha / Form Test") browser.go_to("https://www.google.com/recaptcha/api2/demo") #browser.go_to("https://practicetestautomation.com/practice-test-login/") @@ -456,4 +467,4 @@ if __name__ == "__main__": inputs = ['[input1](Martin)', f'[input2](Test)', '[input3](test@gmail.com)'] browser.fill_form_inputs(inputs) browser.find_and_click_submission() - time.sleep(30) + time.sleep(10) diff --git a/sources/interaction.py b/sources/interaction.py index c5820bc..2f07e81 100644 --- a/sources/interaction.py +++ b/sources/interaction.py @@ -58,6 +58,8 @@ class Interaction: def load_last_session(self): """Recover the last session.""" for agent in self.agents: + if agent.type == "planner_agent": + continue agent.memory.load_memory(agent.type) def save_session(self): @@ -91,6 +93,8 @@ class Interaction: self.recorder.join() self.transcriber.join() query = self.transcriber.get_transcript() + if query == "exit" or query == "goodbye": + return None return query def get_user(self) -> str: diff --git a/sources/llm_provider.py b/sources/llm_provider.py index 2b5af9a..65b9228 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -27,10 +27,11 @@ class Provider: "lm-studio": self.lm_studio_fn, "huggingface": self.huggingface_fn, "deepseek": self.deepseek_fn, + "dsk_deepseek": self.dsk_deepseek, "test": self.test_fn } self.api_key = None - self.unsafe_providers = ["openai", "deepseek"] + self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek"] if self.provider_name not in self.available_providers: raise ValueError(f"Unknown provider: {provider_name}") if self.provider_name in self.unsafe_providers: @@ -245,6 +246,42 @@ class Provider: raise Exception(f"An error occurred: {str(e)}") from e return thought + def dsk_deepseek(self, history, verbose = False): + """ + Use: xtekky/deepseek4free + For free api. Api key should be set to DSK_DEEPSEEK_API_KEY + This is an unofficial provider, you'll have to find how to set it up yourself. + """ + from dsk.api import ( + DeepSeekAPI, + AuthenticationError, + RateLimitError, + NetworkError, + CloudflareError, + APIError + ) + thought = "" + message = '\n---\n'.join([f"{msg['role']}: {msg['content']}" for msg in history]) + + try: + api = DeepSeekAPI(self.api_key) + chat_id = api.create_chat_session() + for chunk in api.chat_completion(chat_id, message): + if chunk['type'] == 'text': + thought += chunk['content'] + return thought + except AuthenticationError: + raise AuthenticationError("Authentication failed. Please check your token.") from e + except RateLimitError: + raise RateLimitError("Rate limit exceeded. Please wait before making more requests.") from e + except CloudflareError as e: + raise CloudflareError(f"Cloudflare protection encountered: {str(e)}") from e + except NetworkError: + raise NetworkError("Network error occurred. Check your internet connection.") from e + except APIError as e: + raise APIError(f"API error occurred: {str(e)}") from e + return None + def test_fn(self, history, verbose = True): """ This function is used to conduct tests. @@ -270,6 +307,6 @@ goodbye! return thought if __name__ == "__main__": - provider = Provider("server", "deepseek-r1:1.5b", "192.168.1.20:3333") + provider = Provider("ollama", "deepseek-r1:1.5b", "127.0.0.1:11434") res = provider.respond(["user", "Hello, how are you?"]) print("Response:", res) diff --git a/sources/memory.py b/sources/memory.py index 5c0af05..9929203 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -15,7 +15,7 @@ from sources.utility import timer_decorator, pretty_print class Memory(): """ Memory is a class for managing the conversation memory - It provides a method to compress the memory (experimental, use with caution). + It provides a method to compress the memory using summarization model. """ def __init__(self, system_prompt: str, recover_last_session: bool = False, @@ -38,6 +38,7 @@ class Memory(): self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model) def get_filename(self) -> str: + """Get the filename for the save file.""" return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt" def save_memory(self, agent_type: str = "casual_agent") -> None: @@ -103,6 +104,7 @@ class Memory(): self.memory = [] def clear_section(self, start: int, end: int) -> None: + """Clear a section of the memory.""" self.memory = self.memory[:start] + self.memory[end:] def get(self) -> list: @@ -134,23 +136,23 @@ class Memory(): inputs = self.tokenizer(input_text, return_tensors="pt", max_length=512, truncation=True) summary_ids = self.model.generate( inputs['input_ids'], - max_length=max_length, # Maximum length of the summary - min_length=min_length, # Minimum length of the summary - length_penalty=1.0, # Adjusts length preference - num_beams=4, # Beam search for better quality - early_stopping=True # Stop when all beams finish + max_length=max_length, + min_length=min_length, + length_penalty=1.0, + num_beams=4, + early_stopping=True ) summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True) summary.replace('summary:', '') return summary - @timer_decorator + #@timer_decorator def compress(self) -> str: """ Compress the memory using the AI model. """ for i in range(len(self.memory)): - if i < 3: + if i < 2: continue if self.memory[i]['role'] == 'system': continue @@ -179,10 +181,12 @@ Use the -I flag to specify the directory containing helper_functions.h. Ensure the file exists in the specified location. """ - memory.push('user', "why do i get this error?") + memory.push('user', "hello") + memory.push('assistant', "how can i help you?") + memory.push('user', "why do i get this cuda error?") memory.push('assistant', sample_text) print("\n---\nmemory before:", memory.get()) memory.compress() print("\n---\nmemory after:", memory.get()) - memory.save_memory() + #memory.save_memory() \ No newline at end of file diff --git a/sources/speech_to_text.py b/sources/speech_to_text.py index 5b8b9ce..c4c7d4b 100644 --- a/sources/speech_to_text.py +++ b/sources/speech_to_text.py @@ -97,7 +97,7 @@ class Transcript: return "cuda:0" else: return "cpu" - + def remove_hallucinations(self, text: str) -> str: """Remove model hallucinations from the text.""" # TODO find a better way to do this diff --git a/sources/text_to_speech.py b/sources/text_to_speech.py index 9087fb2..3d17a7c 100644 --- a/sources/text_to_speech.py +++ b/sources/text_to_speech.py @@ -1,3 +1,4 @@ +import os import re import platform import subprocess @@ -8,6 +9,11 @@ from kokoro import KPipeline from IPython.display import display, Audio import soundfile as sf +if __name__ == "__main__": + from utility import pretty_print, animate_thinking +else: + from sources.utility import pretty_print, animate_thinking + class Speech(): """ Speech is a class for generating speech from text. @@ -29,8 +35,19 @@ class Speech(): self.pipeline = KPipeline(lang_code=self.lang_map[language]) self.voice = self.voice_map[language][voice_idx] self.speed = 1.2 + self.voice_folder = ".voices" + self.create_voice_folder(self.voice_folder) + + def create_voice_folder(self, path: str = ".voices") -> None: + """ + Create a folder to store the voices. + Args: + path (str): The path to the folder. + """ + if not os.path.exists(path): + os.makedirs(path) - def speak(self, sentence: str, voice_number: int = 1 , audio_file: str = 'sample.wav'): + def speak(self, sentence: str, voice_number: int = 1): """ Convert text to speech using an AI model and play the audio. @@ -40,7 +57,11 @@ class Speech(): """ if not self.pipeline: return + if voice_number >= len(self.voice_map[self.language]) or voice_number < 0: + pretty_print("Invalid voice number, using default voice", color="error") + voice_number = 0 sentence = self.clean_sentence(sentence) + audio_file = f"{self.voice_folder}/sample_{self.voice_map[self.language][voice_number]}.wav" self.voice = self.voice_map[self.language][voice_number] generator = self.pipeline( sentence, voice=self.voice, @@ -123,12 +144,18 @@ class Speech(): if __name__ == "__main__": speech = Speech() - tosay = """ + tosay_en = """ I looked up recent news using the website https://www.theguardian.com/world - Here is how to list files: - ls -l -a -h - the ip address of the server is 192.168.1.1 """ - for voice_idx in range (len(speech.voice_map["english"])): - print(f"Voice {voice_idx}") - speech.speak(tosay, voice_idx) + tosay_zh = """ + 我使用网站 https://www.theguardian.com/world 查阅了最近的新闻。 + """ + tosay_fr = """ + J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world + """ + spk = Speech(enable=True, language="en", voice_idx=0) + spk.speak(tosay_en) + spk = Speech(enable=True, language="fr", voice_idx=0) + spk.speak(tosay_fr) + spk = Speech(enable=True, language="zh", voice_idx=0) + spk.speak(tosay_zh) \ No newline at end of file