From 68ed1834a9ce9d22e5e263611d0304c70b9237fa Mon Sep 17 00:00:00 2001 From: martin legrand Date: Thu, 1 May 2025 18:17:03 +0200 Subject: [PATCH 1/6] fix : errors related to API based LLMs --- README.md | 8 +++++--- sources/agents/planner_agent.py | 2 +- sources/llm_provider.py | 8 +++++++- sources/tools/tools.py | 2 +- tests/test_browser_agent_parsing.py | 4 ++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 4c975c3..f9faead 100644 --- a/README.md +++ b/README.md @@ -159,9 +159,9 @@ Set the desired provider in the `config.ini`. See below for a list of API provid ```sh [MAIN] is_local = False -provider_name = openai -provider_model = gpt-4o -provider_server_address = 127.0.0.1:5000 +provider_name = google +provider_model = gemini-2.0-flash +provider_server_address = 127.0.0.1:5000 # doesn't matter ``` Warning: Make sure there is not trailing space in the config. @@ -179,6 +179,8 @@ Example: export `TOGETHER_API_KEY="xxxxx"` | togetherAI | No | Use together AI API (non-private) | | google | No | Use google gemini API (non-private) | +*We advice against using gpt-4o or other closedAI models*, performance are poor for web browsing and task planning. + Next step: [Start services and run AgenticSeek](#Start-services-and-Run) *See the **Known issues** section if you are having issues* diff --git a/sources/agents/planner_agent.py b/sources/agents/planner_agent.py index afb91d2..2b6a34c 100644 --- a/sources/agents/planner_agent.py +++ b/sources/agents/planner_agent.py @@ -151,7 +151,7 @@ class PlannerAgent(Agent): return [] agents_tasks = self.parse_agent_tasks(answer) if agents_tasks == []: - prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n" + prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n" pretty_print("Failed to make plan. Retrying...", color="warning") continue self.show_plan(agents_tasks, answer) diff --git a/sources/llm_provider.py b/sources/llm_provider.py index 4b55bcb..32b8279 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -72,6 +72,8 @@ class Provider: except ModuleNotFoundError as e: raise ModuleNotFoundError(f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?") except Exception as e: + if "try again later" in str(e).lower(): + return f"{self.provider_name} server is overloaded. Please try again later." if "refused" in str(e): return f"Server {self.server_ip} seem offline. Unable to answer." raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e @@ -214,7 +216,7 @@ class Provider: """ base_url = self.server_ip if self.is_local: - raise Exception("Google Gemini is not available for local use.") + raise Exception("Google Gemini is not available for local use. Change config.ini") client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/") try: @@ -237,6 +239,8 @@ class Provider: """ from together import Together client = Together(api_key=self.api_key) + if self.is_local: + raise Exception("Together AI is not available for local use. Change config.ini") try: response = client.chat.completions.create( @@ -257,6 +261,8 @@ class Provider: Use deepseek api to generate text. """ client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com") + if self.is_local: + raise Exception("Deepseek (API) is not available for local use. Change config.ini") try: response = client.chat.completions.create( model="deepseek-chat", diff --git a/sources/tools/tools.py b/sources/tools/tools.py index 3b71a5f..e3b5b86 100644 --- a/sources/tools/tools.py +++ b/sources/tools/tools.py @@ -14,7 +14,7 @@ For example: print("Hello world") ``` This is then executed by the tool with its own class implementation of execute(). -A tool is not just for code tool but also API, internet, etc.. +A tool is not just for code tool but also API, internet search, MCP, etc.. """ import sys diff --git a/tests/test_browser_agent_parsing.py b/tests/test_browser_agent_parsing.py index e8c5b9f..aac95bc 100644 --- a/tests/test_browser_agent_parsing.py +++ b/tests/test_browser_agent_parsing.py @@ -17,13 +17,13 @@ class TestBrowserAgentParsing(unittest.TestCase): # Test various link formats test_text = """ Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com! - Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation. + Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/. """ expected = [ "https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of", "www.google.com", "https://test.org/about?page=1", - "https://weatherstack.com/documentation" + "https://weatherstack.com/documentation", ] result = self.agent.extract_links(test_text) self.assertEqual(result, expected) From c6688355a7a127aa49f3212d379b46dace1e2094 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Thu, 1 May 2025 22:10:54 +0200 Subject: [PATCH 2/6] set code safety on by default --- sources/agents/agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources/agents/agent.py b/sources/agents/agent.py index 4e10c59..b4672a6 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -123,6 +123,8 @@ class Agent(): """ start_tag = "" end_tag = "" + if text is None: + return None start_idx = text.find(start_tag) end_idx = text.rfind(end_tag)+8 return text[start_idx:end_idx] @@ -240,7 +242,7 @@ class Agent(): if blocks != None: for block in blocks: self.show_block(block) - output = tool.execute([block]) + output = tool.execute([block], safety=True) feedback = tool.interpreter_feedback(output) # tool interpreter feedback success = not tool.execution_failure_check(output) self.blocks_result.append(executorResult(block, feedback, success, name)) From 0579fd3bb6effe24cb11bc50ed9c7cb89f2a0614 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Thu, 1 May 2025 22:14:59 +0200 Subject: [PATCH 3/6] upd readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f9faead..3959ec3 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,8 @@ Example: export `TOGETHER_API_KEY="xxxxx"` *We advice against using gpt-4o or other closedAI models*, performance are poor for web browsing and task planning. +Please also note that coding/bash might fail with gemini, it seem to ignore our prompt for format to respect, which are optimized for deepseek r1. + Next step: [Start services and run AgenticSeek](#Start-services-and-Run) *See the **Known issues** section if you are having issues* From 3cf1cab68f3a93879c9c6215e9ea0fe3425b28f7 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Thu, 1 May 2025 22:16:55 +0200 Subject: [PATCH 4/6] cmv --- sources/agents/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/agents/agent.py b/sources/agents/agent.py index b4672a6..f3efb41 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -242,7 +242,7 @@ class Agent(): if blocks != None: for block in blocks: self.show_block(block) - output = tool.execute([block], safety=True) + output = tool.execute([block]) feedback = tool.interpreter_feedback(output) # tool interpreter feedback success = not tool.execution_failure_check(output) self.blocks_result.append(executorResult(block, feedback, success, name)) From ed76c8415b4acc9db6bf9c0db5d4dc8fa9c4ca55 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Fri, 2 May 2025 14:10:16 +0200 Subject: [PATCH 5/6] feat : fileFinder read pdf, browser better chromedriver install --- sources/browser.py | 30 ++++++++++++++++------ sources/text_to_speech.py | 1 + sources/tools/BashInterpreter.py | 13 +++++----- sources/tools/C_Interpreter.py | 10 ++++---- sources/tools/GoInterpreter.py | 10 ++++---- sources/tools/JavaInterpreter.py | 10 ++++---- sources/tools/PyInterpreter.py | 8 +++--- sources/tools/fileFinder.py | 44 +++++++++++++++++++++++++++----- sources/tools/flightSearch.py | 10 ++++---- sources/tools/mcpFinder.py | 11 ++++---- sources/tools/searxSearch.py | 8 +++--- sources/tools/tools.py | 4 +++ sources/tools/webSearch.py | 10 ++------ 13 files changed, 106 insertions(+), 63 deletions(-) diff --git a/sources/browser.py b/sources/browser.py index 0351cb9..a5dc67f 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -65,6 +65,25 @@ def get_random_user_agent() -> str: ] return random.choice(user_agents) +def install_chromedriver() -> str: + """ + Install the ChromeDriver if not already installed. Return the path. + """ + chromedriver_path = shutil.which("chromedriver") + if not chromedriver_path: + try: + chromedriver_path = chromedriver_autoinstaller.install() + except Exception as e: + raise FileNotFoundError( + "ChromeDriver not found and could not be installed automatically. " + "Please install it manually from https://chromedriver.chromium.org/downloads." + "and ensure it's in your PATH or specify the path directly." + "See know issues in readme if your chrome version is above 115." + ) from e + if not chromedriver_path: + raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.") + return chromedriver_path + def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome: """Create a Chrome WebDriver with specified options.""" chrome_options = Options() @@ -97,14 +116,9 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure") else: chrome_options.add_extension(crx_path) - - chromedriver_path = shutil.which("chromedriver") - if not chromedriver_path: - chromedriver_path = chromedriver_autoinstaller.install() - - if not chromedriver_path: - raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.") - + + chromedriver_path = install_chromedriver() + service = Service(chromedriver_path) if stealth_mode: chrome_options.add_argument("--disable-blink-features=AutomationControlled") diff --git a/sources/text_to_speech.py b/sources/text_to_speech.py index e881f50..e50d742 100644 --- a/sources/text_to_speech.py +++ b/sources/text_to_speech.py @@ -140,6 +140,7 @@ class Speech(): return sentence if __name__ == "__main__": + # TODO add info message for cn2an, jieba chinese related import sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) speech = Speech() tosay_en = """ diff --git a/sources/tools/BashInterpreter.py b/sources/tools/BashInterpreter.py index cbc644a..e6ae113 100644 --- a/sources/tools/BashInterpreter.py +++ b/sources/tools/BashInterpreter.py @@ -1,15 +1,14 @@ -import sys +import os, sys import re from io import StringIO import subprocess -if __name__ == "__main__": - from tools import Tools - from safety import is_unsafe -else: - from sources.tools.tools import Tools - from sources.tools.safety import is_unsafe +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools +from sources.tools.safety import is_unsafe class BashInterpreter(Tools): """ diff --git a/sources/tools/C_Interpreter.py b/sources/tools/C_Interpreter.py index 45c501f..2c0cbf9 100644 --- a/sources/tools/C_Interpreter.py +++ b/sources/tools/C_Interpreter.py @@ -1,12 +1,12 @@ import subprocess -import os +import os, sys import tempfile import re -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools class CInterpreter(Tools): """ diff --git a/sources/tools/GoInterpreter.py b/sources/tools/GoInterpreter.py index 2f6053b..ab23f76 100644 --- a/sources/tools/GoInterpreter.py +++ b/sources/tools/GoInterpreter.py @@ -1,12 +1,12 @@ import subprocess -import os +import os, sys import tempfile import re -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools class GoInterpreter(Tools): """ diff --git a/sources/tools/JavaInterpreter.py b/sources/tools/JavaInterpreter.py index a6bbc0a..4658c13 100644 --- a/sources/tools/JavaInterpreter.py +++ b/sources/tools/JavaInterpreter.py @@ -1,12 +1,12 @@ import subprocess -import os +import os, sys import tempfile import re -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools class JavaInterpreter(Tools): """ diff --git a/sources/tools/PyInterpreter.py b/sources/tools/PyInterpreter.py index 350e590..2b4f965 100644 --- a/sources/tools/PyInterpreter.py +++ b/sources/tools/PyInterpreter.py @@ -4,10 +4,10 @@ import os import re from io import StringIO -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools class PyInterpreter(Tools): """ diff --git a/sources/tools/fileFinder.py b/sources/tools/fileFinder.py index 90130b7..1977395 100644 --- a/sources/tools/fileFinder.py +++ b/sources/tools/fileFinder.py @@ -1,13 +1,12 @@ -import os +import os, sys import stat import mimetypes import configparser -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) +from sources.tools.tools import Tools class FileFinder(Tools): """ @@ -30,14 +29,45 @@ class FileFinder(Tools): return file.read() except Exception as e: return f"Error reading file: {e}" + + def read_arbitrary_file(self, file_path: str, file_type: str) -> str: + """ + Reads the content of a file with arbitrary encoding. + Args: + file_path (str): The path to the file to read + Returns: + str: The content of the file in markdown format + """ + content_raw = self.read_file(file_path) + language = ["python", "bash", "javascript", "html", "css", "json"] + if "text" in file_type: + content = content_raw + elif any(lang in file_type for lang in language): + content = content_raw + elif "pdf" in file_type: + from pypdf import PdfReader + reader = PdfReader(file_path) + content = '\n'.join([pt.extract_text() for pt in reader.pages]) + elif "binary" in file_type: + content = content_raw.decode('utf-8', errors='replace') + else: + content = f"Can't read file: ![{file_type}]({file_path})" + return content def get_file_info(self, file_path: str) -> str: + """ + Gets information about a file, including its name, path, type, content, and permissions. + Args: + file_path (str): The path to the file + Returns: + str: A dictionary containing the file information + """ if os.path.exists(file_path): stats = os.stat(file_path) permissions = oct(stat.S_IMODE(stats.st_mode)) file_type, _ = mimetypes.guess_type(file_path) file_type = file_type if file_type else "Unknown" - content = self.read_file(file_path) + content = self.read_arbitrary_file(file_path, file_type) result = { "filename": os.path.basename(file_path), @@ -148,7 +178,7 @@ if __name__ == "__main__": tool = FileFinder() result = tool.execute([""" action=read -name=tools.py +name=cover_letter_martin_legrand.pdf """], False) print("Execution result:") print(result) diff --git a/sources/tools/flightSearch.py b/sources/tools/flightSearch.py index 43246f1..3c6f759 100644 --- a/sources/tools/flightSearch.py +++ b/sources/tools/flightSearch.py @@ -1,13 +1,13 @@ -import os +import os, sys import requests import dotenv dotenv.load_dotenv() -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools class FlightSearch(Tools): def __init__(self, api_key: str = None): diff --git a/sources/tools/mcpFinder.py b/sources/tools/mcpFinder.py index c055405..96e07f5 100644 --- a/sources/tools/mcpFinder.py +++ b/sources/tools/mcpFinder.py @@ -1,12 +1,13 @@ -import os +import os, sys import requests from urllib.parse import urljoin from typing import Dict, Any, Optional -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +from sources.tools.tools import Tools + +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + class MCP_finder(Tools): """ diff --git a/sources/tools/searxSearch.py b/sources/tools/searxSearch.py index 83a6203..15a4438 100644 --- a/sources/tools/searxSearch.py +++ b/sources/tools/searxSearch.py @@ -2,10 +2,10 @@ import requests from bs4 import BeautifulSoup import os -if __name__ == "__main__": - from tools import Tools -else: - from sources.tools.tools import Tools +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) + +from sources.tools.tools import Tools class searxSearch(Tools): def __init__(self, base_url: str = None): diff --git a/sources/tools/tools.py b/sources/tools/tools.py index e3b5b86..80bf25a 100644 --- a/sources/tools/tools.py +++ b/sources/tools/tools.py @@ -21,6 +21,10 @@ import sys import os import configparser from abc import abstractmethod + +if __name__ == "__main__": # if running as a script for individual testing + sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + from sources.logger import Logger class Tools(): diff --git a/sources/tools/webSearch.py b/sources/tools/webSearch.py index 6cf6e5f..3da083d 100644 --- a/sources/tools/webSearch.py +++ b/sources/tools/webSearch.py @@ -5,14 +5,8 @@ import dotenv dotenv.load_dotenv() -if __name__ == "__main__": - import sys - sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) - from utility import animate_thinking, pretty_print - from tools import Tools -else: - from sources.tools.tools import Tools - from sources.utility import animate_thinking, pretty_print +from sources.tools.tools import Tools +from sources.utility import animate_thinking, pretty_print """ WARNING From f17dc0550b8965fd46eb400f75cd7b26e77a81cb Mon Sep 17 00:00:00 2001 From: martin legrand Date: Fri, 2 May 2025 14:23:00 +0200 Subject: [PATCH 6/6] feat: better fileFinder read --- sources/tools/fileFinder.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/sources/tools/fileFinder.py b/sources/tools/fileFinder.py index 1977395..2b30667 100644 --- a/sources/tools/fileFinder.py +++ b/sources/tools/fileFinder.py @@ -38,12 +38,13 @@ class FileFinder(Tools): Returns: str: The content of the file in markdown format """ + mime_type, _ = mimetypes.guess_type(file_path) + if mime_type: + if mime_type.startswith(('image/', 'video/', 'audio/')): + return "can't read file type: image, video, or audio files are not supported." content_raw = self.read_file(file_path) - language = ["python", "bash", "javascript", "html", "css", "json"] if "text" in file_type: content = content_raw - elif any(lang in file_type for lang in language): - content = content_raw elif "pdf" in file_type: from pypdf import PdfReader reader = PdfReader(file_path) @@ -51,7 +52,7 @@ class FileFinder(Tools): elif "binary" in file_type: content = content_raw.decode('utf-8', errors='replace') else: - content = f"Can't read file: ![{file_type}]({file_path})" + content = content_raw return content def get_file_info(self, file_path: str) -> str: @@ -178,7 +179,7 @@ if __name__ == "__main__": tool = FileFinder() result = tool.execute([""" action=read -name=cover_letter_martin_legrand.pdf +name=tools.py """], False) print("Execution result:") print(result)