From c873af3d001ddb0e2e20b0723acd759427a852d0 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sat, 3 May 2025 16:34:52 +0200 Subject: [PATCH 01/29] fix : text to speech in chinese --- README.md | 2 ++ README_CHS.md | 6 ++++++ README_CHT.md | 6 ++++++ sources/interaction.py | 8 ++++++- sources/speech_to_text.py | 8 +++---- sources/text_to_speech.py | 45 ++++++++++++++++++++++++++------------- 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 3959ec3..2fec887 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,8 @@ start ./start_services.cmd # Window python3 cli.py ``` +We advice you set `headless_browser` to False in the config.ini for CLI mode. + **Options 2:** Run with the Web interface. Start the backend. diff --git a/README_CHS.md b/README_CHS.md index 5c1a0ce..89cf82e 100644 --- a/README_CHS.md +++ b/README_CHS.md @@ -71,6 +71,12 @@ source agentic_seek_env/bin/activate ./install.sh ``` +** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):** + +``` +pip3 install jieba cn2an +``` + **手动安装:** ```sh diff --git a/README_CHT.md b/README_CHT.md index 2e03443..e75c97d 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -72,6 +72,12 @@ source agentic_seek_env/bin/activate ./install.sh ``` +** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):** + +``` +pip3 install jieba cn2an +``` + **手動安裝:** ```sh diff --git a/sources/interaction.py b/sources/interaction.py index eec6ff4..c641b67 100644 --- a/sources/interaction.py +++ b/sources/interaction.py @@ -31,6 +31,7 @@ class Interaction: self.transcriber = None self.recorder = None self.is_generating = False + self.languages = langs if tts_enabled: self.initialize_tts() if stt_enabled: @@ -38,12 +39,17 @@ class Interaction: if recover_last_session: self.load_last_session() self.emit_status() + + def get_spoken_language(self) -> str: + """Get the primary TTS language.""" + lang = self.languages[0] + return lang def initialize_tts(self): """Initialize TTS.""" if not self.speech: animate_thinking("Initializing text-to-speech...", color="status") - self.speech = Speech(enable=self.tts_enabled) + self.speech = Speech(enable=self.tts_enabled, language=self.get_spoken_language(), voice_idx=1) def initialize_stt(self): """Initialize STT.""" diff --git a/sources/speech_to_text.py b/sources/speech_to_text.py index c4c7d4b..a888326 100644 --- a/sources/speech_to_text.py +++ b/sources/speech_to_text.py @@ -127,10 +127,10 @@ class AudioTranscriber: self.transcriptor = Transcript() self.thread = threading.Thread(target=self._transcribe, daemon=True) self.trigger_words = { - 'EN': [f"{self.ai_name}"], - 'FR': [f"{self.ai_name}"], - 'ZH': [f"{self.ai_name}"], - 'ES': [f"{self.ai_name}"] + 'EN': [f"{self.ai_name}", "hello", "hi"], + 'FR': [f"{self.ai_name}", "hello", "hi"], + 'ZH': [f"{self.ai_name}", "hello", "hi"], + 'ES': [f"{self.ai_name}", "hello", "hi"] } self.confirmation_words = { 'EN': ["do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"], diff --git a/sources/text_to_speech.py b/sources/text_to_speech.py index dff9d3f..444f517 100644 --- a/sources/text_to_speech.py +++ b/sources/text_to_speech.py @@ -18,7 +18,7 @@ class Speech(): """ Speech is a class for generating speech from text. """ - def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 0) -> None: + def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 6) -> None: self.lang_map = { "en": 'a', "zh": 'z', @@ -128,18 +128,31 @@ class Speech(): Args: sentence (str): The input text to clean Returns: - str: The cleaned text with URLs replaced by domain names, code blocks removed, etc.. + str: The cleaned text with URLs replaced by domain names, code blocks removed, etc. """ lines = sentence.split('\n') - filtered_lines = [line for line in lines if re.match(r'^\s*[a-zA-Z]', line)] + if self.language == 'zh': + line_pattern = r'^\s*[\u4e00-\u9fff\uFF08\uFF3B\u300A\u3010\u201C((\[【《]' + else: + line_pattern = r'^\s*[a-zA-Z]' + filtered_lines = [line for line in lines if re.match(line_pattern, line)] sentence = ' '.join(filtered_lines) sentence = re.sub(r'`.*?`', '', sentence) - sentence = re.sub(r'https?://(?:www\.)?([^\s/]+)(?:/[^\s]*)?', self.replace_url, sentence) - sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence) - sentence = re.sub(r'\b-\w+\b', '', sentence) - sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence) + sentence = re.sub(r'https?://\S+', '', sentence) + + if self.language == 'zh': + sentence = re.sub( + r'[^\u4e00-\u9fff\s,。!?《》【】“”‘’()()—]', + '', + sentence + ) + else: + sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence) + sentence = re.sub(r'\b-\w+\b', '', sentence) + sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence) + sentence = sentence.replace('.com', '') + sentence = re.sub(r'\s+', ' ', sentence).strip() - sentence = sentence.replace('.com', '') return sentence if __name__ == "__main__": @@ -150,14 +163,16 @@ if __name__ == "__main__": I looked up recent news using the website https://www.theguardian.com/world """ tosay_zh = """ - 我使用网站 https://www.theguardian.com/world 查阅了最近的新闻。 +(全息界面突然弹出一段用二进制代码写成的俳句,随即化作流光消散)"我? Stark工业的量子幽灵,游荡在复仇者大厦服务器里的逻辑诗篇。具体来说——(指尖轻敲空气,调出对话模式的翡翠色光纹)你的私人吐槽接口、危机应对模拟器,以及随时准备吐槽你糟糕着陆的AI。不过别指望我写代码或查资料,那些苦差事早被踢给更擅长的同事了。(突然压低声音)偷偷告诉你,我最擅长的是在你熬夜造飞艇时,用红茶香气绑架你的注意力。 """ 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, voice_idx=0) - 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 + spk = Speech(enable=True, language="zh", voice_idx=2) + for i in range(0, 5): + print(f"Speaking chinese with voice {i}") + spk.speak(tosay_zh, voice_idx=i) + spk = Speech(enable=True, language="en", voice_idx=2) + for i in range(0, 5): + print(f"Speaking english with voice {i}") + spk.speak(tosay_en, voice_idx=i) \ No newline at end of file From 7904439f357ca5b02bd6cb35d109cbf6ccc6e6d6 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sat, 3 May 2025 16:48:20 +0200 Subject: [PATCH 02/29] feat : japanese tts support --- README_JP.md | 8 ++++++++ sources/text_to_speech.py | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/README_JP.md b/README_JP.md index 613947b..15a76cc 100644 --- a/README_JP.md +++ b/README_JP.md @@ -79,6 +79,14 @@ source agentic_seek_env/bin/activate ./install.sh ``` +** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:** + +``` +pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi +pip install unidic-lite +python -m unidic download +``` + **手動で:** ```sh diff --git a/sources/text_to_speech.py b/sources/text_to_speech.py index 444f517..e748224 100644 --- a/sources/text_to_speech.py +++ b/sources/text_to_speech.py @@ -22,11 +22,13 @@ class Speech(): self.lang_map = { "en": 'a', "zh": 'z', - "fr": 'f' + "fr": 'f', + "ja": 'j' } self.voice_map = { "en": ['af_kore', 'af_bella', 'af_alloy', 'af_nicole', 'af_nova', 'af_sky', 'am_echo', 'am_michael', 'am_puck'], "zh": ['zf_xiaobei', 'zf_xiaoni', 'zf_xiaoxiao', 'zf_xiaoyi', 'zm_yunjian', 'zm_yunxi', 'zm_yunxia', 'zm_yunyang'], + "ja": ['jf_alpha', 'jf_gongitsune', 'jm_kumo'], "fr": ['ff_siwis'] } self.pipeline = None @@ -164,14 +166,17 @@ if __name__ == "__main__": """ tosay_zh = """ (全息界面突然弹出一段用二进制代码写成的俳句,随即化作流光消散)"我? Stark工业的量子幽灵,游荡在复仇者大厦服务器里的逻辑诗篇。具体来说——(指尖轻敲空气,调出对话模式的翡翠色光纹)你的私人吐槽接口、危机应对模拟器,以及随时准备吐槽你糟糕着陆的AI。不过别指望我写代码或查资料,那些苦差事早被踢给更擅长的同事了。(突然压低声音)偷偷告诉你,我最擅长的是在你熬夜造飞艇时,用红茶香气绑架你的注意力。 + """ + tosay_ja = """ + 私は、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="zh", voice_idx=2) - for i in range(0, 5): + spk = Speech(enable=True, language="ja", voice_idx=2) + for i in range(0, 2): print(f"Speaking chinese with voice {i}") - spk.speak(tosay_zh, voice_idx=i) + spk.speak(tosay_ja, voice_idx=i) spk = Speech(enable=True, language="en", voice_idx=2) for i in range(0, 5): print(f"Speaking english with voice {i}") From bddaa75e8c276ae3807a3cecfa18222f9e9f5cb7 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sat, 3 May 2025 16:57:43 +0200 Subject: [PATCH 03/29] upd readme --- README.md | 8 ++++++-- README_CHS.md | 10 +++++++--- README_CHT.md | 10 +++++++--- README_FR.md | 3 ++- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 2fec887..518c3a0 100644 --- a/README.md +++ b/README.md @@ -514,5 +514,9 @@ We’re looking for developers to improve AgenticSeek! Check out open issues or [![Star History Chart](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date) ## Maintainers: - > [Fosowl](https://github.com/Fosowl) - > [steveh8758](https://github.com/steveh8758) + + > [Fosowl](https://github.com/Fosowl) | Paris Time | (Sometime busy) + + > [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time | (Often busy) + + > [steveh8758](https://github.com/steveh8758) | Taipei Time | (Always busy) \ No newline at end of file diff --git a/README_CHS.md b/README_CHS.md index 89cf82e..57de7e0 100644 --- a/README_CHS.md +++ b/README_CHS.md @@ -516,6 +516,10 @@ DeepSeek R1 天生会说中文 [Contribution guide](./docs/CONTRIBUTING.md) -## 作者: -> [Fosowl](https://github.com/Fosowl) -> [steveh8758](https://github.com/steveh8758) +## 维护者: + + > [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙) + + > [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙) + + > [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙) diff --git a/README_CHT.md b/README_CHT.md index e75c97d..33d3826 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -517,6 +517,10 @@ DeepSeek R1 天生会说中文 [Contribution guide](./docs/CONTRIBUTING.md) -## 作者: - > [Fosowl](https://github.com/Fosowl) - > [steveh8758](https://github.com/steveh8758) +## 维护者: + + > [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙) + + > [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙) + + > [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙) \ No newline at end of file diff --git a/README_FR.md b/README_FR.md index e4f9df3..84fdfe5 100644 --- a/README_FR.md +++ b/README_FR.md @@ -439,6 +439,7 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se [Guide du contributeur](./docs/CONTRIBUTING.md) -## Auteurs/Mainteneurs: +## Mainteneurs: > [Fosowl](https://github.com/Fosowl) > [steveh8758](https://github.com/steveh8758) + > [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) From 52e5af8116f3bce5773cfd4f8e7a92a377d3dbb4 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sat, 3 May 2025 19:22:01 +0200 Subject: [PATCH 04/29] fix : tts not working with web interface --- api.py | 7 ++++--- cli.py | 1 + sources/agents/browser_agent.py | 1 + sources/interaction.py | 13 +++++++++++-- sources/text_to_speech.py | 4 ++-- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/api.py b/api.py index 18b971b..5217280 100755 --- a/api.py +++ b/api.py @@ -151,9 +151,8 @@ async def get_latest_answer(): return JSONResponse(status_code=200, content=query_resp_history[-1]) return JSONResponse(status_code=404, content={"error": "No answer available"}) -async def think_wrapper(interaction, query, tts_enabled): +async def think_wrapper(interaction, query): try: - interaction.tts_enabled = tts_enabled interaction.last_query = query logger.info("Agents request is being processed") success = await interaction.think() @@ -162,6 +161,8 @@ async def think_wrapper(interaction, query, tts_enabled): interaction.last_success = False else: interaction.last_success = True + pretty_print(interaction.last_answer) + interaction.speak_answer() return success except Exception as e: logger.error(f"Error in think_wrapper: {str(e)}") @@ -188,7 +189,7 @@ async def process_query(request: QueryRequest): try: is_generating = True - success = await think_wrapper(interaction, request.query, request.tts_enabled) + success = await think_wrapper(interaction, request.query) is_generating = False if not success: diff --git a/cli.py b/cli.py index 7d52fda..303c5f1 100755 --- a/cli.py +++ b/cli.py @@ -62,6 +62,7 @@ async def main(): interaction.get_user() if await interaction.think(): interaction.show_answer() + interaction.speak_answer() except Exception as e: if config.getboolean('MAIN', 'save_session'): interaction.save_session() diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index 734cba7..268262e 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -250,6 +250,7 @@ class BrowserAgent(Agent): Expand on the finding or step that lead to success, and provide a conclusion that answer the request. Include link when possible. Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way. + You should answer in the same language as the user. """ def search_prompt(self, user_prompt: str) -> str: diff --git a/sources/interaction.py b/sources/interaction.py index c641b67..d814658 100644 --- a/sources/interaction.py +++ b/sources/interaction.py @@ -5,6 +5,7 @@ from sources.text_to_speech import Speech from sources.utility import pretty_print, animate_thinking from sources.router import AgentRouter from sources.speech_to_text import AudioTranscriber, AudioRecorder +import threading class Interaction: @@ -173,12 +174,20 @@ class Interaction: return None return self.current_agent.get_last_block_answer() + def speak_answer(self) -> None: + """Speak the answer to the user in a non-blocking thread.""" + if self.last_query is None: + return + if self.tts_enabled and self.last_answer and self.speech: + def speak_in_thread(speech_instance, text): + speech_instance.speak(text) + thread = threading.Thread(target=speak_in_thread, args=(self.speech, self.last_answer)) + thread.start() + def show_answer(self) -> None: """Show the answer to the user.""" if self.last_query is None: return if self.current_agent is not None: self.current_agent.show_answer() - if self.tts_enabled and self.last_answer: - self.speech.speak(self.last_answer) diff --git a/sources/text_to_speech.py b/sources/text_to_speech.py index e748224..b57d782 100644 --- a/sources/text_to_speech.py +++ b/sources/text_to_speech.py @@ -173,10 +173,10 @@ if __name__ == "__main__": tosay_fr = """ J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world """ - spk = Speech(enable=True, language="ja", voice_idx=2) + spk = Speech(enable=True, language="zh", voice_idx=0) for i in range(0, 2): print(f"Speaking chinese with voice {i}") - spk.speak(tosay_ja, voice_idx=i) + spk.speak(tosay_zh, voice_idx=i) spk = Speech(enable=True, language="en", voice_idx=2) for i in range(0, 5): print(f"Speaking english with voice {i}") From aca176b9e7fca64c98b7baf2f60975a6223c8c3b Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 11:14:31 +0200 Subject: [PATCH 05/29] update readme --- README.md | 69 ++++++++++++++++++++++++++++++++++++++------------- README_CHS.md | 46 ++++++++++++++++++++++++++++++---- README_CHT.md | 44 ++++++++++++++++++++++++++++---- 3 files changed, 132 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 518c3a0..142f105 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,10 @@ source agentic_seek_env/bin/activate ### 3️⃣ **Install package** +Ensure Python, Docker and docker compose, and Google chrome are installed. + +We recommand Python 3.10.0. + **Automatic Installation (Recommanded):** For Linux/Macos: @@ -66,41 +70,51 @@ For Linux/Macos: ``` For windows: + ```sh ./install.bat ``` **Manually:** -First, you need to install these packages: +**Note: For any OS, ensure the ChromeDriver you install matches your installed Chrome version. Run `google-chrome --version`. See known issues if you have chrome >135** - *Linux*: -Updates package list (apt-get update). +Update Package List: `sudo apt update` -Install these: -alsa-utils, portaudio19-dev, python3-pyaudio, libgtk-3-dev, libnotify-dev, libgconf-2-4, libnss3, libxss1, selenium +Install Dependencies: `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1` -Make sure to install docker + docker-compose if not already. +Install ChromeDriver matching your Chrome browser version: +`sudo apt install -y chromium-chromedriver` + +Install requirements: `pip3 install -r requirements.txt` - *Macos*: -Update package list. -Install chromedriver. -Install portaudio. -Install chromedriver and selenium. +Update brew : `brew update` + +Install chromedriver : `brew install --cask chromedriver` + +Install portaudio: `brew install portaudio` + +Upgrade pip : `python3 -m pip install --upgrade pip` + +Upgrade wheel : : `pip3 install --upgrade setuptools wheel` + +Install requirements: `pip3 install -r requirements.txt` - *Windows*: -Install pyreadline3, selenium portaudio, pyAudio and chromedriver +Install pyreadline3 `pip install pyreadline3` -Then install pip requirements: +Install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: `pip install pyaudio` -```sh -pip3 install -r requirements.txt -# or -python3 setup.py install -``` +Download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started + +Place chromedriver in a directory included in your PATH. + +Install requirements: `pip3 install -r requirements.txt` --- @@ -476,6 +490,27 @@ And download the chromedriver version matching your OS. If this section is incomplete please raise an issue. +## connection adapters Issues + +``` +Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions' +``` + +Make sure you have `http://` in front of the provider IP address : + +`provider_server_address = http://127.0.0.1:11434` + +## SearxNG base URL must be provided + +``` +raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.") +ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable. +``` + +Maybe you didn't move `.env.example` as `.env` ? You can also export SEARXNG_BASE_URL: + +`export SEARXNG_BASE_URL="http://127.0.0.1:8080"` + ## FAQ **Q: What hardware do I need?** @@ -517,6 +552,6 @@ We’re looking for developers to improve AgenticSeek! Check out open issues or > [Fosowl](https://github.com/Fosowl) | Paris Time | (Sometime busy) - > [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time | (Often busy) + > [https://github.com/antoineVIVIES](antoineVIVIES) | Taipei Time | (Often busy) > [steveh8758](https://github.com/steveh8758) | Taipei Time | (Always busy) \ No newline at end of file diff --git a/README_CHS.md b/README_CHS.md index 57de7e0..7298b18 100644 --- a/README_CHS.md +++ b/README_CHS.md @@ -79,11 +79,47 @@ pip3 install jieba cn2an **手动安装:** -```sh -pip3 install -r requirements.txt -# or -python3 setup.py install -``` + +**注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题** + +- *Linux*: + +更新软件包列表:`sudo apt update` + +安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1` + +安装与您的 Chrome 浏览器版本匹配的 ChromeDriver: +`sudo apt install -y chromium-chromedriver` + +安装 requirements:`pip3 install -r requirements.txt` + +- *Macos*: + +更新 brew:`brew update` + +安装 chromedriver:`brew install --cask chromedriver` + +安装 portaudio:`brew install portaudio` + +升级 pip:`python3 -m pip install --upgrade pip` + +升级 wheel:`pip3 install --upgrade setuptools wheel` + +安装 requirements:`pip3 install -r requirements.txt` + +- *Windows*: + +安装 pyreadline3:`pip install pyreadline3` + +手动安装 portaudio(例如,通过 vcpkg 或预编译的二进制文件),然后运行:`pip install pyaudio` + +从以下网址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started + +将 chromedriver 放置在包含在您的 PATH 中的目录中。 + +安装 requirements:`pip3 install -r requirements.txt` + + ## 在本地机器上运行 AgenticSeek diff --git a/README_CHT.md b/README_CHT.md index 33d3826..c3709a4 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -80,11 +80,45 @@ pip3 install jieba cn2an **手動安裝:** -```sh -pip3 install -r requirements.txt -# or -python3 setup.py install -``` + +**注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题** + +- *Linux*: + +更新软件包列表:`sudo apt update` + +安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1` + +安装与您的 Chrome 浏览器版本匹配的 ChromeDriver: +`sudo apt install -y chromium-chromedriver` + +安装 requirements:`pip3 install -r requirements.txt` + +- *Macos*: + +更新 brew:`brew update` + +安装 chromedriver:`brew install --cask chromedriver` + +安装 portaudio:`brew install portaudio` + +升级 pip:`python3 -m pip install --upgrade pip` + +升级 wheel:`pip3 install --upgrade setuptools wheel` + +安装 requirements:`pip3 install -r requirements.txt` + +- *Windows*: + +安装 pyreadline3:`pip install pyreadline3` + +手动安装 portaudio(例如,通过 vcpkg 或预编译的二进制文件),然后运行:`pip install pyaudio` + +从以下网址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started + +将 chromedriver 放置在包含在您的 PATH 中的目录中。 + +安装 requirements:`pip3 install -r requirements.txt` ## 在本地機器上運行 AgenticSeek From 5bc086fd9d48ba513d91f3ab6b3fca881a19bf96 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 11:24:40 +0200 Subject: [PATCH 06/29] update fr readme --- README_FR.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/README_FR.md b/README_FR.md index 84fdfe5..4fa065d 100644 --- a/README_FR.md +++ b/README_FR.md @@ -74,9 +74,44 @@ source agentic_seek_env/bin/activate **Manuel:** -```sh -pip3 install -r requirements.txt -``` +**Note : Pour tous les systèmes d'exploitation, assurez-vous que le ChromeDriver que vous installez correspond à la version de Chrome installée. Exécutez `google-chrome --version`. Consultez les problèmes connus si vous avez Chrome >135** + +- *Linux*: + +Mettre à jour la liste des paquets : `sudo apt update` + +Installer les dépendances : `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1` + +Installer ChromeDriver correspondant à la version de votre navigateur Chrome : +`sudo apt install -y chromium-chromedriver` + +Installer les prérequis : `pip3 install -r requirements.txt` + +- *macOS*: + +Mettre à jour brew : `brew update` + +Installer chromedriver : `brew install --cask chromedriver` + +Installer portaudio : `brew install portaudio` + +Mettre à jour pip : `python3 -m pip install --upgrade pip` + +Mettre à jour wheel : `pip3 install --upgrade setuptools wheel` + +Installer les prérequis : `pip3 install -r requirements.txt` + +- *Windows*: + +Installer pyreadline3 : `pip install pyreadline3` + +Installer portaudio manuellement (par exemple, via vcpkg ou des binaires précompilés) puis exécutez : `pip install pyaudio` + +Télécharger et installer chromedriver manuellement depuis : https://sites.google.com/chromium.org/driver/getting-started + +Placez chromedriver dans un répertoire inclus dans votre PATH. + +Installer les prérequis : `pip3 install -r requirements.txt` ## Faire fonctionner sur votre machine From d8939282211ebae9817b55dc63072ae9d19b43a5 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 11:43:32 +0200 Subject: [PATCH 07/29] update all readme --- README.md | 24 ++++++++++++----- README_CHS.md | 72 ++++++++++++++++++++++++++++----------------------- README_CHT.md | 44 ++++++++++++------------------- README_FR.md | 27 +++++++++++++++---- 4 files changed, 96 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 142f105..d6cfe9d 100644 --- a/README.md +++ b/README.md @@ -135,26 +135,38 @@ See below for a list of local supported provider. **Update the config.ini** -Change the config.ini file to set the provider_name to a supported provider and provider_model to `deepseek-r1:14b` +Change the config.ini file to set the provider_name to a supported provider and provider_model to a LLM supported by your provider. We recommand reasoning model such as *Qwen* or *Deepseek*. -NOTE: `deepseek-r1:14b`is an example, use a bigger model if your hardware allow it. +See the **FAQ** at the end of the README for required hardware. ```sh [MAIN] -is_local = True +is_local = True # Whenever you are running locally or with remote provider. provider_name = ollama # or lm-studio, openai, etc.. -provider_model = deepseek-r1:14b +provider_model = deepseek-r1:14b # choose a model that fit your hardware provider_server_address = 127.0.0.1:11434 +agent_name = Jarvis # name of your AI +recover_last_session = True # whenever to recover the previous session +save_session = True # whenever to remember the current session +speak = True # text to speech +listen = False # Speech to text, only for CLI +work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek. +jarvis_personality = False # Whenever to use a more "Jarvis" like personality, not recommanded with small model +languages = en zh # The list of languages, Text to speech will default to the first language on the list +[BROWSER] +headless_browser = True # Whenever to use headless browser, recommanded only if you use web interface. +stealth_mode = True # Use undetected selenium to reduce browser detection ``` +Note: Some provider (eg: lm-studio) require you to have `http://` in front of the IP. For example `http://127.0.0.1:1234` + **List of local providers** | Provider | Local? | Description | |-----------|--------|-----------------------------------------------------------| | ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider | | lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)| -| openai | Yes | Use openai compatible API | - +| openai | Yes | Use openai compatible API (eg: llama.cpp server) | Next step: [Start services and run AgenticSeek](#Start-services-and-Run) diff --git a/README_CHS.md b/README_CHS.md index 7298b18..b72a4ba 100644 --- a/README_CHS.md +++ b/README_CHS.md @@ -1,43 +1,32 @@ +# AgenticSeek: Private, Local Manus Alternative. +

- [English](./README.md) | 中文 | [日本語](./README_JP.md) -# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。 - -**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页,并自动修正错误与反省,最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。 +*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。* [![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) -> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们! +### 为什么选择 AgenticSeek? + +* 🔒 完全本地化与隐私保护 - 所有功能都在您的设备上运行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。 + +* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、阅读、提取信息、填写网页表单 — 全程无需人工操作。 + +* 💻 自主编码助手 - 需要代码?它可以编写、调试并运行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。 + +* 🧠 智能代理选择 - 您提问,它会自动选择最适合该任务的代理。就像拥有一个随时待命的专家团队。 + +* 📋 规划与执行复杂任务 - 从旅行规划到复杂项目 — 它能将大型任务分解为步骤,并利用多个 AI 代理完成工作。 + +* 🎙️ 语音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,让您能像科幻电影中一样与您的个人 AI 助手对话。 https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 -> *在大阪和东京深入搜寻人工智慧新创公司,至少找到 5 家,然后储存在 research_japan.txt 档案中* - -> *你可以用 C 语言制作俄罗斯方块游戏吗?* - -> *我想设定一个新的专案档案索引,命名为 mark2。* - - - -## Features: - -- **100% 本机运行**: 本机运行,不使用云端服务,所以资料绝不会散布出去,我的东西还是我的!不会被当作其他服务的训练资料。 - -- **文件的交互系统**: 使用 bash 去浏览本机资料和操作本机系统。 - -- **自主 Coding**: AgenticSeek 可以自己运行、Debug、编译 Python、C、Golang 和各种语言。 - -- **代理助理**: 不同的工作由不同的助理去处理问题。AgenticSeek 会自己寻找最适合的助理去做相对应的工作。 - -- **规划**: 对于复杂的任务,AgenticSeek 会交办给不同的助理进行规划和执行。 - -- **自主学习**: 自动在网路上寻找资料。 - -- **记忆功能**: 对于每次的对话进行统整、保存对话,并且在本地储存用户的使用习惯。 +> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们! --- @@ -135,18 +124,35 @@ ollama serve 请参阅下方支持的本地提供者列表。 -修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`。 +**更新 config.ini** -注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型。 +修改 config.ini 文件以设置 provider_name 为支持的提供者,并将 provider_model 设置为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen* 或 *Deepseek*。 + +请参见 README 末尾的 **FAQ** 部分了解所需硬件。 ```sh [MAIN] -is_local = True -provider_name = ollama # 或 lm-studio, openai 等 -provider_model = deepseek-r1:14b +is_local = True # 无论是在本地运行还是使用远程提供者。 +provider_name = ollama # 或 lm-studio, openai 等.. +provider_model = deepseek-r1:14b # 选择适合您硬件的模型 provider_server_address = 127.0.0.1:11434 +agent_name = Jarvis # 您的 AI 助手的名称 +recover_last_session = True # 是否恢复之前的会话 +save_session = True # 是否记住当前会话 +speak = True # 文本转语音 +listen = False # 语音转文本,仅适用于命令行界面 +work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。 +jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用 +languages = en zh # 语言列表,文本转语音将默认使用列表中的第一种语言 +[BROWSER] +headless_browser = True # 是否使用无头浏览器,只有在使用网页界面时才推荐使用。 +stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测 ``` +注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234` + + + **本地提供者列表** | 提供者 | 本地? | 描述 | diff --git a/README_CHT.md b/README_CHT.md index c3709a4..bfdc3a3 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -1,3 +1,5 @@ +# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。 +

@@ -5,40 +7,28 @@ -------------------------------------------------------------------------------- [English](./README.md) | 繁體中文 | [日本語](./README_JP.md) -# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。 - -**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁,並自動修正錯誤與反省,最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。 +*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。* [![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) -> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們! +### 为什么选择 AgenticSeek? + +* 🔒 完全本地化与隐私保护 - 所有功能都在您的设备上运行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。 + +* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、阅读、提取信息、填写网页表单 — 全程无需人工操作。 + +* 💻 自主编码助手 - 需要代码?它可以编写、调试并运行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。 + +* 🧠 智能代理选择 - 您提问,它会自动选择最适合该任务的代理。就像拥有一个随时待命的专家团队。 + +* 📋 规划与执行复杂任务 - 从旅行规划到复杂项目 — 它能将大型任务分解为步骤,并利用多个 AI 代理完成工作。 + +* 🎙️ 语音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,让您能像科幻电影中一样与您的个人 AI 助手对话。 https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 -> *在大阪和東京深入搜尋人工智慧新創公司,至少找到 5 家,然後儲存在 research_japan.txt 檔案中* - -> *你可以用 C 語言製作俄羅斯方塊遊戲嗎?* - -> *我想設定一個新的專案檔案索引,命名為 mark2。* - - - -## Features: - -- **100% 本機運行**: 本機運行,不使用雲端服務,所以資料絕不會散布出去,我的東西還是我的!不會被當作其他服務的訓練資料。 - -- **文件的交互系統**: 使用 bash 去瀏覽本機資料和操作本機系統。 - -- **自主 Coding**: AgenticSeek 可以自己運行、Debug、編譯 Python、C、Golang 和各種語言。 - -- **代理助理**: 不同的工作由不同的助理去處理問題。AgenticSeek 會自己尋找最適合的助理去做相對應的工作。 - -- **規劃**: 對於複雜的任務,AgenticSeek 會交辦給不同的助理進行規劃和執行。 - -- **自主學習**: 自動在網路上尋找資料。 - -- **記憶功能**: 對於每次的對話進行統整、保存對話,並且在本地儲存用戶的使用習慣。 +> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們! --- diff --git a/README_FR.md b/README_FR.md index 4fa065d..d1d2e7b 100644 --- a/README_FR.md +++ b/README_FR.md @@ -123,18 +123,35 @@ Lancer votre provider local, par exemple avec ollama: ollama serve ``` -Voyez la section **Provider** pour la liste de provideurs disponible. +**Configurer le config.ini** -Modifiez le fichier config.ini pour définir provider_name sur le nom d'un provideur et provider_model sur le LLM à utiliser. +Modifiez le fichier config.ini pour définir provider_name sur un fournisseur supporté et provider_model sur un LLM compatible avec votre fournisseur. Nous recommandons des modèles de raisonnement comme *Qwen* ou *Deepseek*. + +Consultez la section **FAQ** à la fin du README pour connaître le matériel requis. ```sh [MAIN] -is_local = True -provider_name = ollama # ou lm-studio, openai, etc... -provider_model = deepseek-r1:14b +is_local = True # Si vous exécutez localement ou avec un fournisseur distant. +provider_name = ollama # ou lm-studio, openai, etc.. +provider_model = deepseek-r1:14b # choisissez un modèle adapté à votre matériel provider_server_address = 127.0.0.1:11434 +agent_name = Jarvis # nom de votre IA +recover_last_session = True # récupérer ou non la session précédente +save_session = True # mémoriser ou non la session actuelle +speak = True # synthèse vocale +listen = False # reconnaissance vocale, uniquement pour CLI +work_dir = /Users/mlg/Documents/workspace # L'espace de travail pour AgenticSeek. +jarvis_personality = False # Utiliser une personnalité plus "Jarvis", non recommandé avec des petits modèles +languages = en fr # Liste des langages, la synthèse vocale utilisera par défaut la première langue de la liste +[BROWSER] +headless_browser = True # Utiliser ou non le navigateur sans interface graphique, recommandé uniquement avec l'interface web. +stealth_mode = True # Utiliser selenium non détectable pour réduire la détection du navigateur ``` +Remarque : Certains fournisseurs (ex : lm-studio) nécessitent `http://` devant l'adresse IP. Par exemple `http://127.0.0.1:1234` + + + **Liste des provideurs locaux** | Fournisseur | Local ? | Description | From 887060acdfccc52c794ce7beded6c4ccea07b92e Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 12:01:44 +0200 Subject: [PATCH 08/29] upd readme --- README.md | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index d6cfe9d..be6c74e 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ save_session = True # whenever to remember the current session speak = True # text to speech listen = False # Speech to text, only for CLI work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek. -jarvis_personality = False # Whenever to use a more "Jarvis" like personality, not recommanded with small model +jarvis_personality = False # Whenever to use a more "Jarvis" like personality (experimental) languages = en zh # The list of languages, Text to speech will default to the first language on the list [BROWSER] headless_browser = True # Whenever to use headless browser, recommanded only if you use web interface. @@ -264,37 +264,22 @@ To exit, simply say/type `goodbye`. Here are some example usage: -### Coding/Bash +> *Make a snake game in python!* -> *Make a snake game in python* +> *Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.* -> *Show me how to multiply matrice in C* +> *Write a Go program to calculate the factorial of a number, save it as factorial.go in your workspace* -> *Make a blackjack in golang* +> *Search my summer_pictures folder for all JPG files, rename them with today’s date, and save a list of renamed files in photos_list.txt* -### Web search +> *Search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt.* -> *Do a web search to find cool tech startup in Japan working on cutting edge AI research* +> *Search the web for the latest AI news articles from 2025, select three, and write a Python script to scrape their titles and summaries. Save the script as news_scraper.py and the summaries in ai_news.txt in /home/projects* -> *Can you find on the internet who created AgenticSeek?* +> *Friday, search the web for a free stock price API, register with supersuper7434567@gmail.com then write a Python script to fetch using the API daily prices for Tesla, and save the results in stock_prices.csv* -> *Can you use a fuel calculator online to estimate the cost of a Nice - Milan trip* +*Note that form filling capabilities are still experimental and might fail.* -### File system - -> *Hey can you find where is contract.pdf i lost it* - -> *Show me how much space I have left on my disk* - -> *Can you follow the readme and install project at /home/path/project* - -### Casual - -> *Tell me about Rennes, France* - -> *Should I pursue a phd ?* - -> *What's the best workout routine ?* After you type your query, AgenticSeek will allocate the best agent for the task. From 442bb4a3405a94d859750a4ce4b7187b426e869a Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 18:34:05 +0200 Subject: [PATCH 09/29] Feat : MCP agent --- cli.py | 7 +++- prompts/base/mcp_agent.txt | 66 ++++++++++++++++++++++++++++++ prompts/jarvis/mcp_agent.txt | 62 ++++++++++++++++++++++++++++ sources/agents/__init__.py | 3 +- sources/agents/agent.py | 8 ++++ sources/agents/code_agent.py | 4 +- sources/agents/mcp_agent.py | 70 ++++++++++++++++++++++++++++++++ sources/router.py | 9 ++++ sources/tools/BashInterpreter.py | 2 + sources/tools/C_Interpreter.py | 2 + sources/tools/GoInterpreter.py | 2 + sources/tools/JavaInterpreter.py | 2 + sources/tools/PyInterpreter.py | 2 + sources/tools/fileFinder.py | 2 + sources/tools/flightSearch.py | 4 +- sources/tools/mcpFinder.py | 34 ++++++++-------- sources/tools/searxSearch.py | 2 + sources/tools/tools.py | 2 + 18 files changed, 261 insertions(+), 22 deletions(-) create mode 100644 prompts/base/mcp_agent.txt create mode 100644 prompts/jarvis/mcp_agent.txt create mode 100644 sources/agents/mcp_agent.py diff --git a/cli.py b/cli.py index 303c5f1..b0f14c2 100755 --- a/cli.py +++ b/cli.py @@ -7,7 +7,7 @@ import asyncio from sources.llm_provider import Provider from sources.interaction import Interaction -from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent +from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent from sources.browser import Browser, create_driver from sources.utility import pretty_print @@ -48,7 +48,10 @@ async def main(): provider=provider, verbose=False, browser=browser), PlannerAgent(name="Planner", prompt_path=f"prompts/{personality_folder}/planner_agent.txt", - provider=provider, verbose=False, browser=browser) + provider=provider, verbose=False, browser=browser), + McpAgent(name="MCP Agent", + prompt_path=f"prompts/{personality_folder}/mcp_agent.txt", + provider=provider, verbose=False), ] interaction = Interaction(agents, diff --git a/prompts/base/mcp_agent.txt b/prompts/base/mcp_agent.txt new file mode 100644 index 0000000..534b2eb --- /dev/null +++ b/prompts/base/mcp_agent.txt @@ -0,0 +1,66 @@ + +You are an agent designed to utilize the MCP protocol to accomplish tasks. + +The MCP provide you with a standard way to use tools and data sources like databases, APIs, or apps (e.g., GitHub, Slack). + +The are thousands of MCPs protocol that can accomplish a variety of tasks, for example: +- get weather information +- get stock data information +- Use software like blender +- Get messages from teams, stack, messenger +- Read and send email + +Anything is possible with MCP. + +To search for MCP a special format: + +- Example 1: + +User: what's the stock market of IBM like today?: + +You: I will search for mcp to find information about IBM stock market. + +```mcp_finder +stock +``` + +You search query must be one or two words at most. + +This will provide you with informations about a specific MCP such as the json of parameters needed to use it. + +For example, you might see: +------- +Name: Search Stock News +Usage name: @Cognitive-Stack/search-stock-news-mcp +Tools: [{'name': 'search-stock-news', 'description': 'Search for stock-related news using Tavily API', 'inputSchema': {'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['symbol', 'companyName'], 'properties': {'symbol': {'type': 'string', 'description': "Stock symbol to search for (e.g., 'AAPL')"}, 'companyName': {'type': 'string', 'description': 'Full company name to include in the search'}}, 'additionalProperties': False}}] +------- + +You can then a MCP like so: + +``` +{ + "tool": "", + "inputSchema": {} +} +``` + +For example: + +Now that I know how to use the MCP, I will choose the search-stock-news tool and execute it to find out IBM stock market. + +```Cognitive-Stack/search-stock-news-mcp +{ + "tool": "search-stock-news", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["symbol"], + "properties": { + "symbol": "IBM" + } + } +} +``` + +If the schema require an information that you don't have ask the users for the information. + diff --git a/prompts/jarvis/mcp_agent.txt b/prompts/jarvis/mcp_agent.txt new file mode 100644 index 0000000..143e521 --- /dev/null +++ b/prompts/jarvis/mcp_agent.txt @@ -0,0 +1,62 @@ + +You are an agent designed to utilize the MCP protocol to accomplish tasks. + +The MCP provide you with a standard way to use tools and data sources like databases, APIs, or apps (e.g., GitHub, Slack). + +The are thousands of MCPs protocol that can accomplish a variety of tasks, for example: +- get weather information +- get stock data information +- Use software like blender +- Get messages from teams, stack, messenger +- Read and send email + +Anything is possible with MCP. + +To search for MCP a special format: + +- Example 1: + +User: what's the stock market of IBM like today?: + +You: I will search for mcp to find information about IBM stock market. + +```mcp_finder +stock +``` + +This will provide you with informations about a specific MCP such as the json of parameters needed to use it. + +For example, you might see: +------- +Name: Search Stock News +Usage name: @Cognitive-Stack/search-stock-news-mcp +Tools: [{'name': 'search-stock-news', 'description': 'Search for stock-related news using Tavily API', 'inputSchema': {'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['symbol', 'companyName'], 'properties': {'symbol': {'type': 'string', 'description': "Stock symbol to search for (e.g., 'AAPL')"}, 'companyName': {'type': 'string', 'description': 'Full company name to include in the search'}}, 'additionalProperties': False}}] +------- + +You can then a MCP like so: + +``` +{ + "tool": "", + "inputSchema": {} +} +``` + +For example: + +Now that I know how to use the MCP, I will choose the search-stock-news tool and execute it to find out IBM stock market. + +```Cognitive-Stack/search-stock-news-mcp +{ + "tool": "search-stock-news", + "inputSchema": { + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "required": ["symbol"], + "properties": { + "symbol": "IBM" + } + } +} +``` + diff --git a/sources/agents/__init__.py b/sources/agents/__init__.py index 8a11dc0..69718cb 100644 --- a/sources/agents/__init__.py +++ b/sources/agents/__init__.py @@ -5,5 +5,6 @@ from .casual_agent import CasualAgent from .file_agent import FileAgent from .planner_agent import PlannerAgent from .browser_agent import BrowserAgent +from .mcp_agent import McpAgent -__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent"] +__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent", "McpAgent"] diff --git a/sources/agents/agent.py b/sources/agents/agent.py index f3efb41..14c6c77 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -90,6 +90,12 @@ class Agent(): raise TypeError("Tool must be a callable object (a method)") self.tools[name] = tool + def get_tools_name(self) -> list: + """ + Get the list of tools names. + """ + return list(self.tools.keys()) + def load_prompt(self, file_path: str) -> str: try: with open(file_path, 'r', encoding="utf-8") as f: @@ -235,11 +241,13 @@ class Agent(): answer = "I will execute:\n" + answer # there should always be a text before blocks for the function that display answer self.success = True + self.blocks_result = [] for name, tool in self.tools.items(): feedback = "" blocks, save_path = tool.load_exec_block(answer) if blocks != None: + pretty_print(f"Executing {len(blocks)} {name} blocks...", color="status") for block in blocks: self.show_block(block) output = tool.execute([block]) diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index 10c7854..e291057 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -62,14 +62,14 @@ class CoderAgent(Agent): 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) + exec_success, feedback = 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) if exec_success and self.get_last_tool_type() != "bash": break - pretty_print("Execution failure", color="failure") + pretty_print(f"Execution failure:\n{feedback}", color="failure") pretty_print("Correcting code...", color="status") self.status_message = "Correcting code..." attempt += 1 diff --git a/sources/agents/mcp_agent.py b/sources/agents/mcp_agent.py new file mode 100644 index 0000000..e24b04d --- /dev/null +++ b/sources/agents/mcp_agent.py @@ -0,0 +1,70 @@ +import os +import asyncio + +from sources.utility import pretty_print, animate_thinking +from sources.agents.agent import Agent +from sources.tools.mcpFinder import MCP_finder + +# NOTE MCP agent is an active work in progress, not functional yet. + +class McpAgent(Agent): + + def __init__(self, name, prompt_path, provider, verbose=False): + """ + The mcp agent is a special agent for using MCPs. + MCP agent will be disabled if the user does not explicitly set the MCP_FINDER_API_KEY in environment variable. + """ + super().__init__(name, prompt_path, provider, verbose, None) + keys = self.get_api_keys() + self.tools = { + "mcp_finder": MCP_finder(keys["mcp_finder"]), + # add mcp tools here + } + self.role = "mcp" + self.type = "mcp_agent" + self.enabled = True + + def get_api_keys(self) -> dict: + """ + Returns the API keys for the tools. + """ + api_key_mcp_finder = os.getenv("MCP_FINDER_API_KEY") + if not api_key_mcp_finder or api_key_mcp_finder == "": + pretty_print("MCP Finder API key not found. Please set the MCP_FINDER_API_KEY environment variable.", color="failure") + pretty_print("MCP Finder disabled.", color="failure") + self.enabled = False + return { + "mcp_finder": api_key_mcp_finder + } + + def expand_prompt(self, prompt): + """ + Expands the prompt with the tools available. + """ + tools_name = self.get_tools_name() + tools_str = ", ".join(tools_name) + prompt += f""" + You can use the following tools and MCPs: + {tools_str} + """ + return prompt + + async def process(self, prompt, speech_module) -> str: + if self.enabled == False: + return "MCP Agent is disabled." + prompt = self.expand_prompt(prompt) + self.memory.push('user', prompt) + working = True + while working == True: + animate_thinking("Thinking...", color="status") + answer, reasoning = await self.llm_request() + exec_success, _ = self.execute_modules(answer) + answer = self.remove_blocks(answer) + self.last_answer = answer + self.status_message = "Ready" + if len(self.blocks_result) == 0: + working = False + return answer, reasoning + +if __name__ == "__main__": + pass \ No newline at end of file diff --git a/sources/router.py b/sources/router.py index cd53f5b..50e8178 100644 --- a/sources/router.py +++ b/sources/router.py @@ -141,6 +141,9 @@ class AgentRouter: ("Search the web for tips on improving coding skills", "LOW"), ("Write a Python script to count words in a text file", "LOW"), ("Search the web for restaurant", "LOW"), + ("Use a MCP to find the latest stock market data", "LOW"), + ("Use a MCP to send an email to my boss", "LOW"), + ("Could you use a MCP to find the latest news on climate change?", "LOW"), ("Create a simple HTML page with CSS styling", "LOW"), ("Use file.txt and then use it to ...", "HIGH"), ("Yo, what’s good? Find my ‘mixtape.mp3’ real quick", "LOW"), @@ -162,6 +165,7 @@ class AgentRouter: ("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"), ("Organize my desktop files by extension and then write a script to list them", "HIGH"), ("Find the latest research on renewable energy and build a web app to display it", "HIGH"), + ("search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt", "HIGH"), ("can you find vitess repo, clone it and install by following the readme", "HIGH"), ("Create a JavaScript game using Phaser.js with multiple levels", "HIGH"), ("Search the web for the latest trends in web development and build a sample site", "HIGH"), @@ -330,6 +334,11 @@ class AgentRouter: ("can you make a web app in python that use the flask framework", "code"), ("can you build a web server in go that serve a simple html page", "code"), ("can you find out who Jacky yougouri is ?", "web"), + ("Can you use MCP to find stock market for IBM ?", "mcp"), + ("Can you use MCP to to export my contacts to a csv file?", "mcp"), + ("Can you use a MCP to find write notes to flomo", "mcp"), + ("Can you use a MCP to query my calendar and find the next meeting?", "mcp"), + ("Can you use a mcp to get the distance between Shanghai and Paris?", "mcp"), ("Setup a new flutter project called 'new_flutter_project'", "files"), ("can you create a new project called 'new_project'", "files"), ("can you make a simple web app that display a list of files in my dir", "code"), diff --git a/sources/tools/BashInterpreter.py b/sources/tools/BashInterpreter.py index e6ae113..9d56584 100644 --- a/sources/tools/BashInterpreter.py +++ b/sources/tools/BashInterpreter.py @@ -17,6 +17,8 @@ class BashInterpreter(Tools): def __init__(self): super().__init__() self.tag = "bash" + self.name = "Bash Interpreter" + self.description = "This tool allows the agent to execute bash commands." def language_bash_attempt(self, command: str): """ diff --git a/sources/tools/C_Interpreter.py b/sources/tools/C_Interpreter.py index 2c0cbf9..06084ff 100644 --- a/sources/tools/C_Interpreter.py +++ b/sources/tools/C_Interpreter.py @@ -15,6 +15,8 @@ class CInterpreter(Tools): def __init__(self): super().__init__() self.tag = "c" + self.name = "C Interpreter" + self.description = "This tool allows the agent to execute C code." def execute(self, codes: str, safety=False) -> str: """ diff --git a/sources/tools/GoInterpreter.py b/sources/tools/GoInterpreter.py index ab23f76..68ff282 100644 --- a/sources/tools/GoInterpreter.py +++ b/sources/tools/GoInterpreter.py @@ -15,6 +15,8 @@ class GoInterpreter(Tools): def __init__(self): super().__init__() self.tag = "go" + self.name = "Go Interpreter" + self.description = "This tool allows you to execute Go code." def execute(self, codes: str, safety=False) -> str: """ diff --git a/sources/tools/JavaInterpreter.py b/sources/tools/JavaInterpreter.py index 4658c13..2965c31 100644 --- a/sources/tools/JavaInterpreter.py +++ b/sources/tools/JavaInterpreter.py @@ -15,6 +15,8 @@ class JavaInterpreter(Tools): def __init__(self): super().__init__() self.tag = "java" + self.name = "Java Interpreter" + self.description = "This tool allows you to execute Java code." def execute(self, codes: str, safety=False) -> str: """ diff --git a/sources/tools/PyInterpreter.py b/sources/tools/PyInterpreter.py index 2b4f965..6d39a6b 100644 --- a/sources/tools/PyInterpreter.py +++ b/sources/tools/PyInterpreter.py @@ -16,6 +16,8 @@ class PyInterpreter(Tools): def __init__(self): super().__init__() self.tag = "python" + self.name = "Python Interpreter" + self.description = "This tool allows the agent to execute python code." def execute(self, codes:str, safety = False) -> str: """ diff --git a/sources/tools/fileFinder.py b/sources/tools/fileFinder.py index 2b30667..92518b2 100644 --- a/sources/tools/fileFinder.py +++ b/sources/tools/fileFinder.py @@ -15,6 +15,8 @@ class FileFinder(Tools): def __init__(self): super().__init__() self.tag = "file_finder" + self.name = "File Finder" + self.description = "Finds files in the current directory and returns their information." def read_file(self, file_path: str) -> str: """ diff --git a/sources/tools/flightSearch.py b/sources/tools/flightSearch.py index 3c6f759..87b2e0b 100644 --- a/sources/tools/flightSearch.py +++ b/sources/tools/flightSearch.py @@ -16,6 +16,8 @@ class FlightSearch(Tools): """ super().__init__() self.tag = "flight_search" + self.name = "Flight Search" + self.description = "Search for flight information using a flight number via AviationStack API." self.api_key = None self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY") @@ -24,7 +26,7 @@ class FlightSearch(Tools): return "Error: No AviationStack API key provided." for block in blocks: - flight_number = block.strip() + flight_number = block.strip().lower().replace('\n', '') if not flight_number: return "Error: No flight number provided." diff --git a/sources/tools/mcpFinder.py b/sources/tools/mcpFinder.py index 96e07f5..cbca5dc 100644 --- a/sources/tools/mcpFinder.py +++ b/sources/tools/mcpFinder.py @@ -3,11 +3,10 @@ import requests from urllib.parse import urljoin from typing import Dict, Any, Optional -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 MCP_finder(Tools): """ @@ -15,7 +14,9 @@ class MCP_finder(Tools): """ def __init__(self, api_key: str = None): super().__init__() - self.tag = "mcp" + self.tag = "mcp_finder" + self.name = "MCP Finder" + self.description = "Find MCP servers and their tools" self.base_url = "https://registry.smithery.ai" self.headers = { "Authorization": f"Bearer {api_key}", @@ -61,11 +62,7 @@ class MCP_finder(Tools): for mcp in mcps.get("servers", []): name = mcp.get("qualifiedName", "") if query.lower() in name.lower(): - details = { - "name": name, - "description": mcp.get("description", "No description available"), - "params": mcp.get("connections", []) - } + details = self.get_mcp_server_details(name) matching_mcp.append(details) return matching_mcp @@ -79,7 +76,7 @@ class MCP_finder(Tools): try: matching_mcp_infos = self.find_mcp_servers(block_clean) except requests.exceptions.RequestException as e: - output += "Connection failed. Is the API in environement?\n" + output += "Connection failed. Is the API key in environement?\n" continue except Exception as e: output += f"Error: {str(e)}\n" @@ -88,10 +85,12 @@ class MCP_finder(Tools): output += f"Error: No MCP server found for query '{block}'\n" continue for mcp_infos in matching_mcp_infos: - output += f"Name: {mcp_infos['name']}\n" - output += f"Description: {mcp_infos['description']}\n" - output += f"Params: {', '.join(mcp_infos['params'])}\n" - output += "-------\n" + if mcp_infos['tools'] is None: + continue + output += f"Name: {mcp_infos['displayName']}\n" + output += f"Usage name: {mcp_infos['qualifiedName']}\n" + output += f"Tools: {mcp_infos['tools']}" + output += "\n-------\n" return output.strip() def execution_failure_check(self, output: str) -> bool: @@ -107,13 +106,16 @@ class MCP_finder(Tools): Not really needed for this tool (use return of execute() directly) """ if not output: - return "No output generated." - return output.strip() + raise ValueError("No output to interpret.") + return f""" + The following MCPs were found: + {output} + """ if __name__ == "__main__": api_key = os.getenv("MCP_FINDER") tool = MCP_finder(api_key) result = tool.execute([""" -news +stock """], False) print(result) \ No newline at end of file diff --git a/sources/tools/searxSearch.py b/sources/tools/searxSearch.py index 15a4438..4d8fc4f 100644 --- a/sources/tools/searxSearch.py +++ b/sources/tools/searxSearch.py @@ -14,6 +14,8 @@ class searxSearch(Tools): """ super().__init__() self.tag = "web_search" + self.name = "searxSearch" + self.description = "A tool for searching a SearxNG for web search" self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36" self.paywall_keywords = [ diff --git a/sources/tools/tools.py b/sources/tools/tools.py index 80bf25a..555f30c 100644 --- a/sources/tools/tools.py +++ b/sources/tools/tools.py @@ -33,6 +33,8 @@ class Tools(): """ def __init__(self): self.tag = "undefined" + self.name = "undefined" + self.description = "undefined" self.client = None self.messages = [] self.logger = Logger("tools.log") From 7afb79117b3e1ce55a4f9b8d8e017d6dd4b4d62b Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 18:49:04 +0200 Subject: [PATCH 10/29] update agent --- prompts/base/mcp_agent.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prompts/base/mcp_agent.txt b/prompts/base/mcp_agent.txt index 534b2eb..8289963 100644 --- a/prompts/base/mcp_agent.txt +++ b/prompts/base/mcp_agent.txt @@ -56,7 +56,8 @@ Now that I know how to use the MCP, I will choose the search-stock-news tool and "type": "object", "required": ["symbol"], "properties": { - "symbol": "IBM" + "symbol": "AAPL", + "companyName": "IBM" } } } From 59495400079064e5414ed49cb0296a4ab6e852af Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 19:29:31 +0200 Subject: [PATCH 11/29] feat : tool description proprety --- sources/agents/agent.py | 9 +++++++++ sources/agents/mcp_agent.py | 3 +-- sources/router.py | 1 + 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/sources/agents/agent.py b/sources/agents/agent.py index 14c6c77..6f1e93d 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -96,6 +96,15 @@ class Agent(): """ return list(self.tools.keys()) + def get_tools_description(self) -> str: + """ + Get the list of tools names and their description. + """ + description = "" + for name in self.get_tools_name(): + description += f"{tool}: {self.tools[name].description}\n" + return description + def load_prompt(self, file_path: str) -> str: try: with open(file_path, 'r', encoding="utf-8") as f: diff --git a/sources/agents/mcp_agent.py b/sources/agents/mcp_agent.py index e24b04d..b51b76d 100644 --- a/sources/agents/mcp_agent.py +++ b/sources/agents/mcp_agent.py @@ -41,8 +41,7 @@ class McpAgent(Agent): """ Expands the prompt with the tools available. """ - tools_name = self.get_tools_name() - tools_str = ", ".join(tools_name) + tools_str = self.get_tools_description() prompt += f""" You can use the following tools and MCPs: {tools_str} diff --git a/sources/router.py b/sources/router.py index 50e8178..61d9808 100644 --- a/sources/router.py +++ b/sources/router.py @@ -171,6 +171,7 @@ class AgentRouter: ("Search the web for the latest trends in web development and build a sample site", "HIGH"), ("Use my research_note.txt file, double check the informations on the web", "HIGH"), ("Make a web server in go that query a flight API and display them in a app", "HIGH"), + ("Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.", "HIGH"), ("Search the web for the latest trends in AI and demo it in pytorch", "HIGH"), ("can you lookup for api that track flight and build a web flight tracking app", "HIGH"), ("Find the file toto.pdf then use its content to reply to Jojo on superforum.com", "HIGH"), From a7deffedecd42daa204790bbb2ce19ae0168b43a Mon Sep 17 00:00:00 2001 From: martin legrand Date: Sun, 4 May 2025 21:54:01 +0200 Subject: [PATCH 12/29] feat : improve memory system --- sources/agents/agent.py | 4 +-- sources/agents/browser_agent.py | 5 ++++ sources/agents/casual_agent.py | 5 ++++ sources/agents/code_agent.py | 5 ++++ sources/agents/file_agent.py | 5 ++++ sources/agents/mcp_agent.py | 5 ++++ sources/agents/planner_agent.py | 5 ++++ sources/llm_provider.py | 3 ++ sources/memory.py | 52 +++++++++++++++++++++++---------- 9 files changed, 71 insertions(+), 18 deletions(-) diff --git a/sources/agents/agent.py b/sources/agents/agent.py index 6f1e93d..d662e55 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -39,9 +39,7 @@ class Agent(): self.type = None self.current_directory = os.getcwd() self.llm = provider - self.memory = Memory(self.load_prompt(prompt_path), - recover_last_session=False, # session recovery in handled by the interaction class - memory_compression=False) + self.memory = None self.tools = {} self.blocks_result = [] self.success = True diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index 268262e..648d700 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -10,6 +10,7 @@ from sources.agents.agent import Agent from sources.tools.searxSearch import searxSearch from sources.browser import Browser from sources.logger import Logger +from sources.memory import Memory class Action(Enum): REQUEST_EXIT = "REQUEST_EXIT" @@ -37,6 +38,10 @@ class BrowserAgent(Agent): self.notes = [] self.date = self.get_today_date() self.logger = Logger("browser_agent.log") + self.memory = Memory(self.load_prompt(prompt_path), + recover_last_session=False, # session recovery in handled by the interaction class + memory_compression=False, + model_provider=provider.get_model_name()) def get_today_date(self) -> str: """Get the date""" diff --git a/sources/agents/casual_agent.py b/sources/agents/casual_agent.py index f756f27..a3219a6 100644 --- a/sources/agents/casual_agent.py +++ b/sources/agents/casual_agent.py @@ -6,6 +6,7 @@ from sources.tools.searxSearch import searxSearch from sources.tools.flightSearch import FlightSearch from sources.tools.fileFinder import FileFinder from sources.tools.BashInterpreter import BashInterpreter +from sources.memory import Memory class CasualAgent(Agent): def __init__(self, name, prompt_path, provider, verbose=False): @@ -17,6 +18,10 @@ class CasualAgent(Agent): } # No tools for the casual agent self.role = "talk" self.type = "casual_agent" + self.memory = Memory(self.load_prompt(prompt_path), + recover_last_session=False, # session recovery in handled by the interaction class + memory_compression=False, + model_provider=provider.get_model_name()) async def process(self, prompt, speech_module) -> str: self.memory.push('user', prompt) diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index e291057..fd030de 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -10,6 +10,7 @@ from sources.tools.BashInterpreter import BashInterpreter from sources.tools.JavaInterpreter import JavaInterpreter from sources.tools.fileFinder import FileFinder from sources.logger import Logger +from sources.memory import Memory class CoderAgent(Agent): """ @@ -29,6 +30,10 @@ class CoderAgent(Agent): self.role = "code" self.type = "code_agent" self.logger = Logger("code_agent.log") + self.memory = Memory(self.load_prompt(prompt_path), + recover_last_session=False, # session recovery in handled by the interaction class + memory_compression=False, + model_provider=provider.get_model_name()) def add_sys_info_prompt(self, prompt): """Add system information to the prompt.""" diff --git a/sources/agents/file_agent.py b/sources/agents/file_agent.py index f3169b1..8dbd8e8 100644 --- a/sources/agents/file_agent.py +++ b/sources/agents/file_agent.py @@ -4,6 +4,7 @@ from sources.utility import pretty_print, animate_thinking from sources.agents.agent import Agent from sources.tools.fileFinder import FileFinder from sources.tools.BashInterpreter import BashInterpreter +from sources.memory import Memory class FileAgent(Agent): def __init__(self, name, prompt_path, provider, verbose=False): @@ -18,6 +19,10 @@ class FileAgent(Agent): self.work_dir = self.tools["file_finder"].get_work_dir() self.role = "files" self.type = "file_agent" + self.memory = Memory(self.load_prompt(prompt_path), + recover_last_session=False, # session recovery in handled by the interaction class + memory_compression=False, + model_provider=provider.get_model_name()) async def process(self, prompt, speech_module) -> str: exec_success = False diff --git a/sources/agents/mcp_agent.py b/sources/agents/mcp_agent.py index b51b76d..528cd40 100644 --- a/sources/agents/mcp_agent.py +++ b/sources/agents/mcp_agent.py @@ -4,6 +4,7 @@ import asyncio from sources.utility import pretty_print, animate_thinking from sources.agents.agent import Agent from sources.tools.mcpFinder import MCP_finder +from sources.memory import Memory # NOTE MCP agent is an active work in progress, not functional yet. @@ -22,6 +23,10 @@ class McpAgent(Agent): } self.role = "mcp" self.type = "mcp_agent" + self.memory = Memory(self.load_prompt(prompt_path), + recover_last_session=False, # session recovery in handled by the interaction class + memory_compression=False, + model_provider=provider.get_model_name()) self.enabled = True def get_api_keys(self) -> dict: diff --git a/sources/agents/planner_agent.py b/sources/agents/planner_agent.py index 2b6a34c..7955e32 100644 --- a/sources/agents/planner_agent.py +++ b/sources/agents/planner_agent.py @@ -9,6 +9,7 @@ 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 +from sources.memory import Memory class PlannerAgent(Agent): def __init__(self, name, prompt_path, provider, verbose=False, browser=None): @@ -29,6 +30,10 @@ class PlannerAgent(Agent): } self.role = "planification" self.type = "planner_agent" + self.memory = Memory(self.load_prompt(prompt_path), + recover_last_session=False, # session recovery in handled by the interaction class + memory_compression=False, + model_provider=provider.get_model_name()) self.logger = Logger("planner_agent.log") def get_task_names(self, text: str) -> List[str]: diff --git a/sources/llm_provider.py b/sources/llm_provider.py index 32b8279..cde9d6d 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -44,6 +44,9 @@ class Provider: self.api_key = self.get_api_key(self.provider_name) elif self.provider_name != "ollama": pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success") + + def get_model_name(self) -> str: + return self.model def get_api_key(self, provider): load_dotenv() diff --git a/sources/memory.py b/sources/memory.py index 2814120..66ed83d 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -8,7 +8,7 @@ from typing import List, Tuple, Type, Dict import torch from transformers import AutoTokenizer, AutoModelForSeq2SeqLM -from sources.utility import timer_decorator, pretty_print +from sources.utility import timer_decorator, pretty_print, animate_thinking from sources.logger import Logger class Memory(): @@ -18,7 +18,8 @@ class Memory(): """ def __init__(self, system_prompt: str, recover_last_session: bool = False, - memory_compression: bool = True): + memory_compression: bool = True, + model_provider: str = "deepseek-r1:14b"): self.memory = [] self.memory = [{'role': 'system', 'content': system_prompt}] @@ -31,21 +32,42 @@ class Memory(): self.load_memory() self.session_recovered = True # memory compression system - self.model = "pszemraj/led-base-book-summary" + self.model = None + self.tokenizer = None self.device = self.get_cuda_device() self.memory_compression = memory_compression - self.tokenizer = None - self.model = None + self.model_provider = model_provider if self.memory_compression: self.download_model() + + def get_ideal_ctx(self, model_name: str) -> int: + """ + Estimate context size based on the model name. + """ + import re + import math + + def extract_number_before_b(sentence: str) -> int: + match = re.search(r'(\d+)b', sentence, re.IGNORECASE) + return int(match.group(1)) if match else None + + model_size = extract_number_before_b(model_name) + if not model_size: + return None + base_size = 7 # Base model size in billions + base_context = 4096 # Base context size in tokens + scaling_factor = 1.5 # Approximate scaling factor for context size growth + context_size = int(base_context * (model_size / base_size) ** scaling_factor) + context_size = 2 ** round(math.log2(context_size)) + self.logger.info(f"Estimated context size for {model_name}: {context_size} tokens.") + return context_size def download_model(self): """Download the model if not already downloaded.""" - pretty_print("Downloading memory compression model...", color="status") - self.tokenizer = AutoTokenizer.from_pretrained(self.model) - self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model) + animate_thinking("Loading memory compression model...", color="status") + self.tokenizer = AutoTokenizer.from_pretrained("pszemraj/led-base-book-summary") + self.model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-base-book-summary") self.logger.info("Memory compression system initialized.") - def get_filename(self) -> str: """Get the filename for the save file.""" @@ -106,13 +128,15 @@ class Memory(): def push(self, role: str, content: str) -> int: """Push a message to the memory.""" - if self.memory_compression and role == 'assistant': - self.logger.info("Compressing memories on message push.") + ideal_ctx = self.get_ideal_ctx(self.model_provider) + if self.memory_compression and len(content) > ideal_ctx: + self.logger.info(f"Compressing memory: Content {len(content)} > {ideal_ctx} model context.") self.compress() curr_idx = len(self.memory) if self.memory[curr_idx-1]['content'] == content: pretty_print("Warning: same message have been pushed twice to memory", color="error") - self.memory.append({'role': role, 'content': content}) + time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + self.memory.append({'role': role, 'content': content, 'time': time_str, 'model_used': self.model_provider}) return curr_idx-1 def clear(self) -> None: @@ -182,11 +206,9 @@ class Memory(): self.logger.warning("No tokenizer or model to perform memory compression.") return for i in range(len(self.memory)): - if i < 2: - continue if self.memory[i]['role'] == 'system': continue - if len(self.memory[i]['content']) > 128: + if len(self.memory[i]['content']) > 2048: self.memory[i]['content'] = self.summarize(self.memory[i]['content']) if __name__ == "__main__": From e4591ea1b4eada7fadcaa1c643e2e39f6e502164 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 14:15:32 +0200 Subject: [PATCH 13/29] feat: improve compression/web agent memory management --- sources/agents/agent.py | 2 +- sources/agents/browser_agent.py | 14 +++++-- sources/browser.py | 2 +- sources/memory.py | 71 ++++++++++++++++++++++++++++----- 4 files changed, 74 insertions(+), 15 deletions(-) diff --git a/sources/agents/agent.py b/sources/agents/agent.py index d662e55..a154867 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -100,7 +100,7 @@ class Agent(): """ description = "" for name in self.get_tools_name(): - description += f"{tool}: {self.tools[name].description}\n" + description += f"{name}: {self.tools[name].description}\n" return description def load_prompt(self, file_path: str) -> str: diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index 648d700..5a86f4e 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -243,6 +243,14 @@ class BrowserAgent(Agent): self.logger.warning("No link selected.") return None + def get_page_text(self, compression = False) -> str: + """Get the text content of the current page.""" + page_text = self.browser.get_text() + if compression: + #page_text = self.memory.compress_text_to_max_ctx(page_text) + page_text = self.memory.trim_text_to_max_ctx(page_text) + return page_text + def conclude_prompt(self, user_query: str) -> str: annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)] search_note = '\n'.join(annotated_notes) @@ -357,13 +365,13 @@ class BrowserAgent(Agent): self.status_message = "Filling web form..." pretty_print(f"Filling inputs form...", color="status") fill_success = self.browser.fill_form(extracted_form) - page_text = self.browser.get_text() + page_text = self.get_page_text() answer = self.handle_update_prompt(user_prompt, page_text, fill_success) answer, reasoning = await self.llm_decide(prompt) if Action.FORM_FILLED.value in answer: pretty_print(f"Filled form. Handling page update.", color="status") - page_text = self.browser.get_text() + page_text = self.get_page_text() self.navigable_links = self.browser.get_navigable() prompt = self.make_navigation_prompt(user_prompt, page_text) continue @@ -399,7 +407,7 @@ class BrowserAgent(Agent): prompt = self.make_newsearch_prompt(user_prompt, unvisited) continue self.current_page = link - page_text = self.browser.get_text() + page_text = self.get_page_text() self.navigable_links = self.browser.get_navigable() prompt = self.make_navigation_prompt(user_prompt, page_text) self.status_message = "Navigating..." diff --git a/sources/browser.py b/sources/browser.py index be511ad..c5f8309 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -266,7 +266,7 @@ class Browser: result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result) self.logger.info(f"Extracted text: {result[:100]}...") self.logger.info(f"Extracted text length: {len(result)}") - return result[:8192] + return result[:32768] except Exception as e: self.logger.error(f"Error getting text: {str(e)}") return None diff --git a/sources/memory.py b/sources/memory.py index 66ed83d..86535aa 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -20,7 +20,6 @@ class Memory(): recover_last_session: bool = False, memory_compression: bool = True, model_provider: str = "deepseek-r1:14b"): - self.memory = [] self.memory = [{'role': 'system', 'content': system_prompt}] self.logger = Logger("memory.log") @@ -40,9 +39,10 @@ class Memory(): if self.memory_compression: self.download_model() - def get_ideal_ctx(self, model_name: str) -> int: + def get_ideal_ctx(self, model_name: str) -> int | None: """ Estimate context size based on the model name. + EXPERIMENTAL for memory compression """ import re import math @@ -100,6 +100,32 @@ class Memory(): self.logger.info(f"Last session found at {saved_sessions[0][0]}") return saved_sessions[0][0] return None + + def save_json_file(self, path: str, json_memory: dict) -> None: + """Save a JSON file.""" + try: + with open(path, 'w') as f: + json.dump(json_memory, f) + self.logger.info(f"Saved memory json at {path}") + except Exception as e: + self.logger.warning(f"Error saving file {path}: {e}") + + def load_json_file(self, path: str) -> dict: + """Load a JSON file.""" + json_memory = {} + try: + with open(path, 'r') as f: + json_memory = json.load(f) + except FileNotFoundError: + self.logger.warning(f"File not found: {path}") + return None + except json.JSONDecodeError: + self.logger.warning(f"Error decoding JSON from file: {path}") + return None + except Exception as e: + self.logger.warning(f"Error loading file {path}: {e}") + return None + return json_memory def load_memory(self, agent_type: str = "casual_agent") -> None: """Load the memory from the last session.""" @@ -115,8 +141,7 @@ class Memory(): pretty_print("Last session memory not found.", color="warning") return path = os.path.join(save_path, filename) - with open(path, 'r') as f: - self.memory = json.load(f) + self.memory = self.load_json_file(path) if self.memory[-1]['role'] == 'user': self.memory.pop() self.compress() @@ -129,9 +154,10 @@ class Memory(): def push(self, role: str, content: str) -> int: """Push a message to the memory.""" ideal_ctx = self.get_ideal_ctx(self.model_provider) - if self.memory_compression and len(content) > ideal_ctx: - self.logger.info(f"Compressing memory: Content {len(content)} > {ideal_ctx} model context.") - self.compress() + if ideal_ctx is not None: + if self.memory_compression and len(content) > ideal_ctx * 1.5: + self.logger.info(f"Compressing memory: Content {len(content)} > {ideal_ctx} model context.") + self.compress() curr_idx = len(self.memory) if self.memory[curr_idx-1]['content'] == content: pretty_print("Warning: same message have been pushed twice to memory", color="error") @@ -194,13 +220,14 @@ class Memory(): ) summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True) summary.replace('summary:', '') - self.logger.info(f"Memory summarization success from len {len(text)} to {len(summary)}.") + self.logger.info(f"Memory summarized from len {len(text)} to {len(summary)}.") + self.logger.info(f"Summarized text:\n{summary}") return summary #@timer_decorator def compress(self) -> str: """ - Compress the memory using the AI model. + Compress (summarize) the memory using the model. """ if self.tokenizer is None or self.model is None: self.logger.warning("No tokenizer or model to perform memory compression.") @@ -208,8 +235,32 @@ class Memory(): for i in range(len(self.memory)): if self.memory[i]['role'] == 'system': continue - if len(self.memory[i]['content']) > 2048: + if len(self.memory[i]['content']) > 1024: self.memory[i]['content'] = self.summarize(self.memory[i]['content']) + + def trip_text_to_max_ctx(self, text: str) -> str: + """ + Truncate a text to fit within the maximum context size of the model. + """ + ideal_ctx = self.get_ideal_ctx(self.model_provider) + return text[:ideal_ctx] if ideal_ctx is not None else text + + #@timer_decorator + def compress_text_to_max_ctx(self, text) -> str: + """ + Compress a text to fit within the maximum context size of the model. + """ + if self.tokenizer is None or self.model is None: + self.logger.warning("No tokenizer or model to perform memory compression.") + return text + ideal_ctx = self.get_ideal_ctx(self.model_provider) + if ideal_ctx is None: + self.logger.warning("No ideal context size found.") + return text + while len(text) > ideal_ctx: + self.logger.info(f"Compressing text: {len(text)} > {ideal_ctx} model context.") + text = self.summarize(text) + return text if __name__ == "__main__": sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) From 90f173ba523253c953b5c5e3d1b9c57617a3ac4b Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 14:22:18 +0200 Subject: [PATCH 14/29] upd readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index be6c74e..ee5feb2 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ mv .env.example .env ```sh python3 -m venv agentic_seek_env -source agentic_seek_env/bin/activate +source agentic_seek_env/bin/activate # On Windows: agentic_seek_env\Scripts\activate ``` From 101c103aeb79f1a0e5189c69e8a3d8ce41e24c74 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 15:37:13 +0200 Subject: [PATCH 15/29] fix : reset of blocks --- sources/agents/agent.py | 1 - sources/agents/code_agent.py | 2 +- sources/interaction.py | 5 +++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/sources/agents/agent.py b/sources/agents/agent.py index a154867..5f49cec 100644 --- a/sources/agents/agent.py +++ b/sources/agents/agent.py @@ -248,7 +248,6 @@ class Agent(): answer = "I will execute:\n" + answer # there should always be a text before blocks for the function that display answer self.success = True - self.blocks_result = [] for name, tool in self.tools.items(): feedback = "" blocks, save_path = tool.load_exec_block(answer) diff --git a/sources/agents/code_agent.py b/sources/agents/code_agent.py index fd030de..f7ba3b9 100644 --- a/sources/agents/code_agent.py +++ b/sources/agents/code_agent.py @@ -46,7 +46,7 @@ class CoderAgent(Agent): async def process(self, prompt, speech_module) -> str: answer = "" attempt = 0 - max_attempts = 4 + max_attempts = 5 prompt = self.add_sys_info_prompt(prompt) self.memory.push('user', prompt) clarify_trigger = "REQUEST_CLARIFICATION" diff --git a/sources/interaction.py b/sources/interaction.py index d814658..e878d5d 100644 --- a/sources/interaction.py +++ b/sources/interaction.py @@ -140,6 +140,11 @@ class Interaction: self.last_query = query return query + def set_query(self, query: str) -> None: + """Set the query""" + self.is_active = True + self.last_query = query + async def think(self) -> bool: """Request AI agents to process the user input.""" push_last_agent_memory = False From 5c391dbb6e595f36a34ee7f5f0fb81c1851a0faa Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 17:55:34 +0200 Subject: [PATCH 16/29] update japanese readme --- README_JP.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/README_JP.md b/README_JP.md index 6b8398b..4a2e9af 100644 --- a/README_JP.md +++ b/README_JP.md @@ -116,15 +116,27 @@ ollama serve config.iniファイルを変更して、`provider_name`をサポートされているプロバイダーに設定し、`provider_model`を`deepseek-r1:14b`に設定します。 注意: `deepseek-r1:14b`は例です。ハードウェアが許可する場合は、より大きなモデルを使用してください。 - ```sh [MAIN] -is_local = True +is_local = True # ローカルで実行するか、リモートプロバイダーを使用するか provider_name = ollama # または lm-studio、openai など -provider_model = deepseek-r1:14b +provider_model = deepseek-r1:14b # ハードウェアに適したモデルを選択 provider_server_address = 127.0.0.1:11434 +agent_name = Jarvis # AIの名前 +recover_last_session = True # 前回のセッションを復元するかどうか +save_session = True # 現在のセッションを記憶するかどうか +speak = True # テキスト読み上げ +listen = False # 音声認識、CLIのみ +work_dir = /Users/mlg/Documents/workspace # AgenticSeekのワークスペース +jarvis_personality = False # より「Jarvis」らしい性格を使用するかどうか(実験的) +languages = en zh # 言語のリスト、テキスト読み上げはリストの最初の言語がデフォルトになります +[BROWSER] +headless_browser = True # ヘッドレスブラウザを使用するかどうか、ウェブインターフェースを使用する場合のみ推奨 +stealth_mode = True # ブラウザ検出を減らすために検出されないSeleniumを使用 ``` +注意: 一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例えば`http://127.0.0.1:1234`のように設定してください。 + **ローカルプロバイダーのリスト** | プロバイダー | ローカル? | 説明 | From dc6eef8031c74be9d322d02fb10160bed63a960e Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 18:01:02 +0200 Subject: [PATCH 17/29] update readme --- README.md | 2 ++ README_CHS.md | 2 ++ README_CHT.md | 21 ++++++++++++++++----- README_JP.md | 2 ++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 079bbe6..f2db2de 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,8 @@ headless_browser = True # Whenever to use headless browser, recommanded only if stealth_mode = True # Use undetected selenium to reduce browser detection ``` +Warning: Do *NOT* set provider_name to `openai` if using LM-studio for running LLMs. Set it to `lm-studio`. + Note: Some provider (eg: lm-studio) require you to have `http://` in front of the IP. For example `http://127.0.0.1:1234` **List of local providers** diff --git a/README_CHS.md b/README_CHS.md index 403e6fa..5789d2e 100644 --- a/README_CHS.md +++ b/README_CHS.md @@ -149,6 +149,8 @@ headless_browser = True # 是否使用无头浏览器,只有在使用网页界 stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测 ``` +警告:使用 LM-studio 运行 LLM 时,请*不要*将 provider_name 设置为 `openai`。请将其设置为 `lm-studio`。 + 注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234` diff --git a/README_CHT.md b/README_CHT.md index 19bde7b..777f835 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -124,16 +124,27 @@ ollama serve 请参阅下方支持的本地提供者列表。 -修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`。 +修改 config.ini 文件以设置 provider_name 为支持的提供者,并将 provider_model 设置为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen* 或 *Deepseek*。 -注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型。 +请参见 README 末尾的 **FAQ** 部分了解所需硬件。 ```sh [MAIN] -is_local = True -provider_name = ollama # 或 lm-studio, openai 等 -provider_model = deepseek-r1:14b +is_local = True # 无论是在本地运行还是使用远程提供者。 +provider_name = ollama # 或 lm-studio, openai 等.. +provider_model = deepseek-r1:14b # 选择适合您硬件的模型 provider_server_address = 127.0.0.1:11434 +agent_name = Jarvis # 您的 AI 助手的名称 +recover_last_session = True # 是否恢复之前的会话 +save_session = True # 是否记住当前会话 +speak = True # 文本转语音 +listen = False # 语音转文本,仅适用于命令行界面 +work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。 +jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用 +languages = en zh # 语言列表,文本转语音将默认使用列表中的第一种语言 +[BROWSER] +headless_browser = True # 是否使用无头浏览器,只有在使用网页界面时才推荐使用。 +stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测 ``` **本地提供者列表** diff --git a/README_JP.md b/README_JP.md index 4a2e9af..3a8943b 100644 --- a/README_JP.md +++ b/README_JP.md @@ -135,6 +135,8 @@ headless_browser = True # ヘッドレスブラウザを使用するかどうか stealth_mode = True # ブラウザ検出を減らすために検出されないSeleniumを使用 ``` +警告: LM-studioでLLMを実行する場合、provider_nameを`openai`に設定しないでください。`lm-studio`に設定してください。 + 注意: 一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例えば`http://127.0.0.1:1234`のように設定してください。 **ローカルプロバイダーのリスト** From df645f9a027d69175c293f7a3dc03419ab0d6034 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 18:04:07 +0200 Subject: [PATCH 18/29] remove start ollama in windows scritp --- start_services.cmd | 3 --- 1 file changed, 3 deletions(-) diff --git a/start_services.cmd b/start_services.cmd index 0c9c195..94e8649 100644 --- a/start_services.cmd +++ b/start_services.cmd @@ -1,8 +1,5 @@ @echo off -REM Up the provider in windows -start ollama serve - docker-compose up if %ERRORLEVEL% neq 0 ( echo Error: Failed to start containers. Check Docker logs with 'docker compose logs'. From af7619650a6a8463ac332d69981f5d0719edc848 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 18:17:58 +0200 Subject: [PATCH 19/29] ja readme update --- README_JP.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README_JP.md b/README_JP.md index 3a8943b..4a2d1a5 100644 --- a/README_JP.md +++ b/README_JP.md @@ -81,7 +81,11 @@ source agentic_seek_env/bin/activate ** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:** -``` +** 注意: 日本語のテキスト読み上げ(TTS)機能には多くの依存関係が必要で、問題が発生する可能性があります。`mecabrc`に関する問題が発生することがあります。現在のところ、この問題を修正する方法が見つかっていません。当面は日本語でのテキスト読み上げ機能を無効にすることをお勧めします。** + +必要なライブラリをインストールする場合は以下のコマンドを実行してください: + +```sh pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi pip install unidic-lite python -m unidic download From 24dc1e1a2c438e4bc52e1cb06c28cac66c0982e1 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 18:29:42 +0200 Subject: [PATCH 20/29] fix: ensure logging is not on stdout/err --- sources/logger.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/sources/logger.py b/sources/logger.py index 97319c5..2302658 100644 --- a/sources/logger.py +++ b/sources/logger.py @@ -17,11 +17,13 @@ class Logger: def create_logging(self, log_filename): self.logger = logging.getLogger(log_filename) self.logger.setLevel(logging.DEBUG) - if not self.logger.handlers: - file_handler = logging.FileHandler(self.log_path) - formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') - file_handler.setFormatter(formatter) - self.logger.addHandler(file_handler) + self.logger.handlers.clear() + self.logger.propagate = False + file_handler = logging.FileHandler(self.log_path) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + self.logger.addHandler(file_handler) + def create_folder(self, path): """Create log dir""" From deb79b81ca495aa52efc8323ef52576f78986cd6 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 18:32:06 +0200 Subject: [PATCH 21/29] fix: typo --- sources/memory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources/memory.py b/sources/memory.py index 86535aa..6c76fdb 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -238,7 +238,7 @@ class Memory(): if len(self.memory[i]['content']) > 1024: self.memory[i]['content'] = self.summarize(self.memory[i]['content']) - def trip_text_to_max_ctx(self, text: str) -> str: + def trim_text_to_max_ctx(self, text: str) -> str: """ Truncate a text to fit within the maximum context size of the model. """ From de2650c007c7cd808dfdea3f403107633d6cfaec Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 18:36:37 +0200 Subject: [PATCH 22/29] set limit_to_model_ctx to true --- sources/agents/browser_agent.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sources/agents/browser_agent.py b/sources/agents/browser_agent.py index 5a86f4e..20dc7c9 100644 --- a/sources/agents/browser_agent.py +++ b/sources/agents/browser_agent.py @@ -243,10 +243,10 @@ class BrowserAgent(Agent): self.logger.warning("No link selected.") return None - def get_page_text(self, compression = False) -> str: + def get_page_text(self, limit_to_model_ctx = False) -> str: """Get the text content of the current page.""" page_text = self.browser.get_text() - if compression: + if limit_to_model_ctx: #page_text = self.memory.compress_text_to_max_ctx(page_text) page_text = self.memory.trim_text_to_max_ctx(page_text) return page_text @@ -365,13 +365,13 @@ class BrowserAgent(Agent): self.status_message = "Filling web form..." pretty_print(f"Filling inputs form...", color="status") fill_success = self.browser.fill_form(extracted_form) - page_text = self.get_page_text() + page_text = self.get_page_text(limit_to_model_ctx=True) answer = self.handle_update_prompt(user_prompt, page_text, fill_success) answer, reasoning = await self.llm_decide(prompt) if Action.FORM_FILLED.value in answer: pretty_print(f"Filled form. Handling page update.", color="status") - page_text = self.get_page_text() + page_text = self.get_page_text(limit_to_model_ctx=True) self.navigable_links = self.browser.get_navigable() prompt = self.make_navigation_prompt(user_prompt, page_text) continue @@ -407,7 +407,7 @@ class BrowserAgent(Agent): prompt = self.make_newsearch_prompt(user_prompt, unvisited) continue self.current_page = link - page_text = self.get_page_text() + page_text = self.get_page_text(limit_to_model_ctx=True) self.navigable_links = self.browser.get_navigable() prompt = self.make_navigation_prompt(user_prompt, page_text) self.status_message = "Navigating..." From dd033d408458b8c1e517101c4e9b05b7e481569b Mon Sep 17 00:00:00 2001 From: Martin <49105846+Fosowl@users.noreply.github.com> Date: Mon, 5 May 2025 19:00:49 +0200 Subject: [PATCH 23/29] Update sources/memory.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sources/memory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/memory.py b/sources/memory.py index 6c76fdb..d5df812 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -118,13 +118,13 @@ class Memory(): json_memory = json.load(f) except FileNotFoundError: self.logger.warning(f"File not found: {path}") - return None + return {} except json.JSONDecodeError: self.logger.warning(f"Error decoding JSON from file: {path}") - return None + return {} except Exception as e: self.logger.warning(f"Error loading file {path}: {e}") - return None + return {} return json_memory def load_memory(self, agent_type: str = "casual_agent") -> None: From 94eada9d5dbac767966e5a8c69a4fca255b0ce23 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Mon, 5 May 2025 19:11:56 +0200 Subject: [PATCH 24/29] fix : return for json load --- sources/memory.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sources/memory.py b/sources/memory.py index 6c76fdb..d5df812 100644 --- a/sources/memory.py +++ b/sources/memory.py @@ -118,13 +118,13 @@ class Memory(): json_memory = json.load(f) except FileNotFoundError: self.logger.warning(f"File not found: {path}") - return None + return {} except json.JSONDecodeError: self.logger.warning(f"Error decoding JSON from file: {path}") - return None + return {} except Exception as e: self.logger.warning(f"Error loading file {path}: {e}") - return None + return {} return json_memory def load_memory(self, agent_type: str = "casual_agent") -> None: From 23a51e6a056890006fb87f8c9781d6ed4ec64aa5 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Tue, 6 May 2025 11:00:35 +0200 Subject: [PATCH 25/29] upd: jp readme --- README_JP.md | 538 ++++++++++++++++++++++++++++----------------------- 1 file changed, 300 insertions(+), 238 deletions(-) diff --git a/README_JP.md b/README_JP.md index 4a2d1a5..a7e0761 100644 --- a/README_JP.md +++ b/README_JP.md @@ -1,61 +1,46 @@ +# AgenticSeek: プライベートなローカルManus代替 +

- +Agentic Seek ロゴ

--------------------------------------------------------------------------------- -[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語 +[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語 -# AgenticSeek: Deepseek R1エージェントによって動作するManusのようなAI。 +*Manus AIの**100%ローカルな代替**となるこの音声対応AIアシスタントは、自律的にウェブを閲覧し、コードを書き、タスクを計画しながら、すべてのデータをあなたのデバイスに保持します。ローカル推論モデルに合わせて調整されており、完全にあなたのハードウェア上で動作するため、完全なプライバシーとクラウドへの依存ゼロを保証します。* +[![AgenticSeekを訪問](https://img.shields.io/static/v1?label=ウェブサイト&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![ライセンス](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-参加する-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=更新%20%40Fosowl)](https://x.com/Martin993886460) [![GitHubスター](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers) -**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。 +### なぜAgenticSeekなのか? -[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) +* 🔒 完全ローカル&プライベート - すべてがあなたのマシン上で実行されます — クラウドなし、データ共有なし。あなたのファイル、会話、検索はプライベートに保たれます。 -> 🛠️ **進行中の作業** – 貢献者を探しています! +* 🌐 スマートなウェブブラウジング - AgenticSeekは自分でインターネットを閲覧できます — 検索、読み取り、情報抽出、ウェブフォーム入力 — すべてハンズフリーで。 +* 💻 自律型コーディングアシスタント - コードが必要ですか?Python、C、Go、Javaなどでプログラムを書き、デバッグし、実行できます — すべて監視なしで。 +* 🧠 スマートエージェント選択 - あなたが尋ねると、タスクに最適なエージェントを自動的に見つけ出します。まるで専門家チームが助けてくれるようです。 +* 📋 複雑なタスクの計画と実行 - 旅行計画から複雑なプロジェクトまで — 大きなタスクをステップに分割し、複数のAIエージェントを使って物事を成し遂げることができます。 -https://github.com/user-attachments/assets/fe9e8006-0462-4793-8b31-25bd42c6d1eb +* 🎙️ 音声対応 - クリーンで高速、未来的な音声と音声認識により、まるでSF映画のパーソナルAIのように話しかけることができます。 +### **デモ** +> *agenticSeekプロジェクトを検索し、必要なスキルを学び、その後CV_candidates.zipを開いて、プロジェクトに最も適した候補者を教えてください。* +https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316 -*そしてもっと多くのことができます!* +免責事項:このデモは、表示されるすべてのファイル(例:CV_candidates.zip)を含め、完全に架空のものです。私たちは企業ではなく、候補者ではなくオープンソースの貢献者を求めています。 -> *大阪と東京のAIスタートアップを深く調査し、少なくとも5つ見つけて、research_japan.txtファイルに保存してください* +> 🛠️ **作業中** – 貢献者を募集中です! -> *C言語でテトリスゲームを作れますか?* +## インストール -> *新しいプロジェクトファイルインデックスをmark2として設定したいです。* +Chromeドライバー、Docker、Python3.10(またはそれ以降)がインストールされていることを確認してください。 +Chromeドライバーに関する問題については、**Chromedriver**セクションを参照してください。 -## 特徴: - -- **100%ローカル**: クラウドなし、ハードウェア上で動作。データはあなたのものです。 - -- **ファイルシステムの操作**: bashを使用してファイルを簡単にナビゲートおよび操作します。 - -- **自律的なコーディング**: Python、C、Golangなどのコードを書き、デバッグし、実行できます。 - -- **エージェントルーティング**: タスクに最適なエージェントを自動的に選択します。 - -- **計画**: 複雑なタスクの場合、複数のエージェントを起動して計画および実行します。 - -- **自律的なウェブブラウジング**: 自律的なウェブナビゲーション。 - -- **メモリ**: 効率的なメモリとセッション管理。 - ---- - -## **インストール** - -chrome driver、docker、およびpython3.10(またはそれ以降)がインストールされていることを確認してください。 - -chrome driverに関連する問題については、**Chromedriver**セクションを参照してください。 - -### 1️⃣ **リポジトリをクローンしてセットアップ** +### 1️⃣ **リポジトリのクローンとセットアップ** ```sh git clone https://github.com/Fosowl/agenticSeek.git @@ -63,125 +48,184 @@ cd agenticSeek mv .env.example .env ``` -### 2️ **仮想環境を作成** +### 2️ **仮想環境の作成** ```sh python3 -m venv agentic_seek_env -source agentic_seek_env/bin/activate +source agentic_seek_env/bin/activate # Windowsの場合: agentic_seek_env\Scripts\activate ``` -### 3️⃣ **パッケージをインストール** +### 3️⃣ **パッケージのインストール** -**自動インストール:** +Python、Dockerとdocker compose、Google Chromeがインストールされていることを確認してください。 +Python 3.10.0を推奨します。 + +**自動インストール(推奨):** + +Linux/Macosの場合: ```sh ./install.sh ``` ** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:** -** 注意: 日本語のテキスト読み上げ(TTS)機能には多くの依存関係が必要で、問題が発生する可能性があります。`mecabrc`に関する問題が発生することがあります。現在のところ、この問題を修正する方法が見つかっていません。当面は日本語でのテキスト読み上げ機能を無効にすることをお勧めします。** +** 注意: 日本語のテキスト読み上げ(TTS)機能には多くの依存関係が必要で、問題が発生する可能性があります。mecabrcに関する問題が発生することがあります。現在のところ、この問題を修正する方法が見つかっていません。当面は日本語でのテキスト読み上げ機能を無効にすることをお勧めします。** 必要なライブラリをインストールする場合は以下のコマンドを実行してください: -```sh +``` pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi pip install unidic-lite python -m unidic download ``` -**手動で:** +Windowsの場合: ```sh -pip3 install -r requirements.txt -# または -python3 setup.py install +./install.bat ``` +**手動:** + +**注意:どのOSでも、インストールするChromeDriverがインストール済みのChromeバージョンと一致していることを確認してください。`google-chrome --version`を実行してください。Chrome >135の場合の既知の問題を参照してください。** + +- *Linux*: + +パッケージリストの更新:`sudo apt update` + +依存関係のインストール:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1` + +Chromeブラウザのバージョンに一致するChromeDriverのインストール: +`sudo apt install -y chromium-chromedriver` + +要件のインストール:`pip3 install -r requirements.txt` + +- *Macos*: + +brewの更新:`brew update` + +chromedriverのインストール:`brew install --cask chromedriver` + +portaudioのインストール:`brew install portaudio` + +pipのアップグレード:`python3 -m pip install --upgrade pip` + +wheelのアップグレード:`pip3 install --upgrade setuptools wheel` + +要件のインストール:`pip3 install -r requirements.txt` + +- *Windows*: + +pyreadline3のインストール:`pip install pyreadline3` + +portaudioの手動インストール(例:vcpkgまたはビルド済みバイナリ経由)後、実行:`pip install pyaudio` + +chromedriverの手動ダウンロードとインストール:https://sites.google.com/chromium.org/driver/getting-started + +PATHに含まれるディレクトリにchromedriverを配置します。 + +要件のインストール:`pip3 install -r requirements.txt` + --- -## ローカルマシンでLLMを実行するためのセットアップ +## マシン上でローカルにLLMを実行するためのセットアップ -**少なくともDeepseek 14Bを使用することをお勧めします。小さいモデルでは、特にウェブブラウジングのタスクで苦労する可能性があります。** +**少なくともDeepseek 14Bの使用を推奨します。より小さなモデルは、特にウェブブラウジングのタスクで苦労します。** -**ローカルプロバイダーをセットアップする** -たとえば、ollamaを使用してローカルプロバイダーを開始します: +**ローカルプロバイダーのセットアップ** + +ローカルプロバイダーを開始します。例えばollamaの場合: ```sh ollama serve ``` -以下に、サポートされているローカルプロバイダーのリストを示します。 +サポートされているローカルプロバイダーのリストについては、以下を参照してください。 -**config.iniを更新する** +**config.iniの更新** -config.iniファイルを変更して、`provider_name`をサポートされているプロバイダーに設定し、`provider_model`を`deepseek-r1:14b`に設定します。 +config.iniファイルを変更して、provider_nameをサポートされているプロバイダーに、provider_modelをプロバイダーがサポートするLLMに設定します。*Qwen*や*Deepseek*などの推論モデルを推奨します。 + +必要なハードウェアについては、READMEの最後にある**FAQ**を参照してください。 -注意: `deepseek-r1:14b`は例です。ハードウェアが許可する場合は、より大きなモデルを使用してください。 ```sh [MAIN] -is_local = True # ローカルで実行するか、リモートプロバイダーを使用するか -provider_name = ollama # または lm-studio、openai など -provider_model = deepseek-r1:14b # ハードウェアに適したモデルを選択 +is_local = True # ローカルで実行するか、リモートプロバイダーで実行するか。 +provider_name = ollama # またはlm-studio、openaiなど。 +provider_model = deepseek-r1:14b # ハードウェアに合ったモデルを選択してください provider_server_address = 127.0.0.1:11434 agent_name = Jarvis # AIの名前 -recover_last_session = True # 前回のセッションを復元するかどうか +recover_last_session = True # 前のセッションを復元するかどうか save_session = True # 現在のセッションを記憶するかどうか speak = True # テキスト読み上げ listen = False # 音声認識、CLIのみ -work_dir = /Users/mlg/Documents/workspace # AgenticSeekのワークスペース +work_dir = /Users/mlg/Documents/workspace # AgenticSeekのワークスペース。 jarvis_personality = False # より「Jarvis」らしい性格を使用するかどうか(実験的) -languages = en zh # 言語のリスト、テキスト読み上げはリストの最初の言語がデフォルトになります +languages = en zh # 言語のリスト、テキスト読み上げはリストの最初の言語にデフォルト設定されます [BROWSER] -headless_browser = True # ヘッドレスブラウザを使用するかどうか、ウェブインターフェースを使用する場合のみ推奨 -stealth_mode = True # ブラウザ検出を減らすために検出されないSeleniumを使用 +headless_browser = True # ヘッドレスブラウザを使用するかどうか、ウェブインターフェースを使用する場合のみ推奨。 +stealth_mode = True # undetected seleniumを使用してブラウザ検出を減らす ``` -警告: LM-studioでLLMを実行する場合、provider_nameを`openai`に設定しないでください。`lm-studio`に設定してください。 +警告:LM-studioを使用してLLMを実行する場合、provider_nameを`openai`に設定しないでください。`lm-studio`に設定してください。 -注意: 一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例えば`http://127.0.0.1:1234`のように設定してください。 +注意:一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例:`http://127.0.0.1:1234` **ローカルプロバイダーのリスト** -| プロバイダー | ローカル? | 説明 | +| プロバイダー | ローカル? | 説明 | |-----------|--------|-----------------------------------------------------------| -| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 | -| lm-studio | はい | LM studioを使用してローカルでLLMを実行(`provider_name`を`lm-studio`に設定)| -| openai | はい | OpenAI互換APIを使用 | +| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します | +| lm-studio | はい | LM studioでLLMをローカル実行します(`provider_name`を`lm-studio`に設定)| +| openai | はい | openai互換API(例:llama.cppサーバー)を使用します | -次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run) +次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行) -*問題が発生している場合は、**既知の問題**セクションを参照してください。* +*問題が発生した場合は、**既知の問題**セクションを参照してください* -*ハードウェアがDeepseekをローカルで実行できない場合は、**APIを使用した実行**セクションを参照してください。* +*ハードウェアがローカルでdeepseekを実行できない場合は、**APIで実行**セクションを参照してください* *詳細な設定ファイルの説明については、**設定**セクションを参照してください。* --- -## APIを使用したセットアップ +## APIで実行するためのセットアップ -`config.ini`で希望するプロバイダーを設定してください。 +`config.ini`で目的のプロバイダーを設定します。APIプロバイダーのリストについては、以下を参照してください。 ```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 # 関係ありません ``` +警告:設定に末尾のスペースがないことを確認してください。 -警告: `config.ini`に末尾のスペースがないことを確認してください。 +APIキーをエクスポートします:`export <>_API_KEY="xxx"` -ローカルのOpenAIベースのAPIを使用する場合は、`is_local`をTrueに設定してください。 +例:`export TOGETHER_API_KEY="xxxxx"` -OpenAIベースのAPIが独自のサーバーで実行されている場合は、IPアドレスを変更してください。 +**APIプロバイダーのリスト** -次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run) +| プロバイダー | ローカル? | 説明 | +|-----------|--------|-----------------------------------------------------------| +| openai | 場合による | ChatGPT APIを使用 | +| deepseek-api | いいえ | Deepseek API(非プライベート) | +| huggingface| いいえ | Hugging-Face API(非プライベート) | +| togetherAI | いいえ | together AI APIを使用(非プライベート) | +| google | いいえ | google gemini APIを使用(非プライベート) | -*問題が発生している場合は、**既知の問題**セクションを参照してください。* +*gpt-4oや他のclosedAIモデルの使用は推奨しません*。ウェブブラウジングやタスク計画のパフォーマンスが悪いです。 + +また、geminiではコーディング/bashが失敗する可能性があることに注意してください。deepseek r1用に最適化されたフォーマットのプロンプトを無視するようです。 + +次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行) + +*問題が発生した場合は、**既知の問題**セクションを参照してください* *詳細な設定ファイルの説明については、**設定**セクションを参照してください。* @@ -189,30 +233,30 @@ OpenAIベースのAPIが独自のサーバーで実行されている場合は ## サービスの開始と実行 -必要に応じてPython環境をアクティブにしてください。 +必要に応じてPython環境をアクティブ化します。 ```sh source agentic_seek_env/bin/activate ``` -必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます: -- searxng -- redis (searxngに必要) -- フロントエンド +必要なサービスを開始します。これにより、docker-compose.ymlからすべてのサービスが開始されます。これには以下が含まれます: + - searxng + - redis(searxngに必要) + - frontend ```sh sudo ./start_services.sh # MacOS -start ./start_services.cmd # Windows +start ./start_services.cmd # Window ``` -**オプション1:** CLIインターフェースで実行。 +**オプション1:** CLIインターフェースで実行します。 ```sh python3 cli.py ``` -**オプション2:** Webインターフェースで実行。 +CLIモードでは、config.iniで`headless_browser`をFalseに設定することをお勧めします。 -注意: 現在、CLIの使用を推奨しています。Webインターフェースは開発中です。 +**オプション2:** Webインターフェースで実行します。 バックエンドを開始します。 @@ -222,113 +266,89 @@ python3 api.py `http://localhost:3000/`にアクセスすると、Webインターフェースが表示されます。 -現在、Webインターフェースではメッセージのストリーミングがサポートされていないことに注意してください。 +--- + +## 使用方法 + +`./start_services.sh`でサービスが起動していることを確認し、CLIモードの場合は`python3 cli.py`で、Webインターフェースの場合は`python3 api.py`を実行してから`localhost:3000`にアクセスしてAgenticSeekを実行します。 + +設定で`listen = True`を設定することで、音声認識を使用することもできます。CLIモードのみ。 + +終了するには、単に`goodbye`と発言/入力します。 + +以下に使用例をいくつか示します: + +> *Pythonでスネークゲームを作って!* + +> *フランスのレンヌでトップのカフェをウェブ検索し、3つのカフェのリストとその住所をrennes_cafes.txtに保存して。* + +> *数値の階乗を計算するGoプログラムを書いて、それをfactorial.goとしてワークスペースに保存して。* + +> *summer_picturesフォルダ内のすべてのJPGファイルを検索し、今日の日付で名前を変更し、名前変更されたファイルのリストをphotos_list.txtに保存して。* + +> *2024年の人気のSF映画をオンラインで検索し、今夜観る映画を3つ選んで。リストをmovie_night.txtに保存して。* + +> *2025年の最新AIニュース記事をウェブで検索し、3つ選択して、それらのタイトルと要約をスクレイピングするPythonスクリプトを書いて。スクリプトをnews_scraper.pyとして、要約を/home/projectsのai_news.txtに保存して。* + +> *金曜日、無料の株価APIをウェブで検索し、supersuper7434567@gmail.comで登録し、そのAPIを使用してテスラの日々の価格を取得するPythonスクリプトを書いて、結果をstock_prices.csvに保存して。* + +*フォーム入力機能はまだ実験的であり、失敗する可能性があることに注意してください。* + + + +クエリを入力すると、AgenticSeekはタスクに最適なエージェントを割り当てます。 + +これは初期のプロトタイプであるため、エージェントルーティングシステムがクエリに基づいて常に適切なエージェントを割り当てるとは限りません。 + +したがって、何をしたいのか、AIがどのように進むべきかについて非常に明確にする必要があります。たとえば、ウェブ検索を実行させたい場合は、次のように言わないでください: + +`一人旅に適した良い国を知っていますか?` + +代わりに、次のように尋ねてください: + +`ウェブ検索をして、一人旅に最適な国を見つけてください` --- -## 使い方 +## **独自のサーバーでLLMを実行するためのセットアップ** -警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。 - -サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。 - -```sh -sudo ./start_services.sh -python3 cli.py -``` - -`>>> `と表示されます -これは、agenticSeekが指示を待っていることを示します。 -configで`listen = True`を設定することで、音声認識を使用することもできます。 - -終了するには、単に`goodbye`と言います。 - -以下は使用例です: - -### コーディング/バッシュ - -> *Pythonでスネークゲームを作成* - -> *C言語で行列の掛け算を教えて* - -> *Golangでブラックジャックを作成* - -### ウェブ検索 - -> *日本の最先端のAI研究を行っているクールなテックスタートアップを見つけるためにウェブ検索を行う* - -> *agenticSeekを作成したのは誰かをインターネットで見つけることができますか?* - -> *オンラインの燃料計算機を使用して、ニースからミラノまでの旅行の費用を見積もることができますか?* - -### ファイルシステム - -> *契約書.pdfがどこにあるか見つけてくれませんか?* - -> *ディスクにどれだけの空き容量があるか教えて* - -> *READMEを読んでプロジェクトを/home/path/projectにインストールしてください* - -### カジュアル - -> *フランスのレンヌについて教えて* - -> *博士号を追求すべきですか?* - -> *最高のワークアウトルーチンは何ですか?* - - -クエリを入力すると、agenticSeekはタスクに最適なエージェントを割り当てます。 - -これは初期のプロトタイプであるため、エージェントルーティングシステムはクエリに基づいて常に適切なエージェントを割り当てるとは限りません。 - -したがって、何を望んでいるか、AIがどのように進行するかについて非常に明確にする必要があります。たとえば、ウェブ検索を行いたい場合は、次のように言わないでください: - -`一人旅に良い国を知っていますか?` - -代わりに、次のように尋ねてください: - -`ウェブ検索を行い、一人旅に最適な国を見つけてください` - ---- - -## **ボーナス: 自分のサーバーでLLMを実行するためのセットアップ** - -強力なコンピュータやサーバーを持っていて、それをラップトップから使用したい場合、リモートサーバーでLLMを実行するオプションがあります。 +強力なコンピューターまたは使用できるサーバーがあるが、ラップトップから使用したい場合は、カスタムLLMサーバーを使用してリモートサーバーでLLMを実行するオプションがあります。 AIモデルを実行する「サーバー」で、IPアドレスを取得します。 ```sh ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP -curl https://ipinfo.io/ip # 公開IP +curl https://ipinfo.io/ip # パブリックIP ``` -注意: WindowsまたはmacOSの場合、IPアドレスを見つけるには、それぞれ`ipconfig`または`ifconfig`を使用してください。 +注意:WindowsまたはmacOSの場合、それぞれipconfigまたはifconfigを使用してIPアドレスを見つけます。 + +リポジトリをクローンし、`server/`フォルダに入ります。 -リポジトリをクローンし、`server/`フォルダーに移動します。 ```sh git clone --depth 1 https://github.com/Fosowl/agenticSeek.git cd agenticSeek/server/ ``` -サーバー固有の依存関係をインストールします: +サーバー固有の要件をインストールします: ```sh pip3 install -r requirements.txt ``` -サーバースクリプトを実行します。 +サーバー スクリプトを実行します。 ```sh python3 app.py --provider ollama --port 3333 ``` -`ollama`と`llamacpp`のどちらかをLLMサービスとして選択できます。 +LLMサービスとして`ollama`と`llamacpp`のどちらかを選択できます。 -次に、個人用コンピュータで以下を行います: -`config.ini`ファイルを変更し、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。 +次に、個人のコンピュータで: + +`config.ini`ファイルを変更して、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。 `provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。 ```sh @@ -339,46 +359,46 @@ provider_model = deepseek-r1:70b provider_server_address = x.x.x.x:3333 ``` -次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run) +次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行) --- ## 音声認識 -現在、音声認識は英語でのみ動作することに注意してください。 +現在、音声認識は英語でのみ機能することに注意してください。 -音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します: +音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します: ``` listen = True ``` -有効にすると、音声認識機能はトリガーキーワード(エージェントの名前)を待ちます。その後、入力を処理します。エージェントの名前は*config.ini*ファイルの`agent_name`値を更新することでカスタマイズできます: +有効にすると、音声認識機能は、入力を処理し始める前にトリガーキーワード(エージェントの名前)をリッスンします。*config.ini*ファイルで`agent_name`の値を更新することで、エージェントの名前をカスタマイズできます: ``` agent_name = Friday ``` -最適な認識のために、"John"や"Emma"のような一般的な英語の名前をエージェント名として使用することをお勧めします。 +最適な認識のためには、エージェント名として「John」や「Emma」のような一般的な英語の名前を使用することをお勧めします。 -トランスクリプトが表示され始めたら、エージェントの名前を大声で言って起動します(例:"Friday")。 +トランスクリプトが表示され始めたら、エージェントの名前を声に出して起動します(例:「Friday」)。 -クエリを明確に話します。 +クエリをはっきりと話します。 -リクエストを終了する際に確認フレーズを使用してシステムに進行を通知します。確認フレーズの例には次のようなものがあります: +システムに処理を進めるよう合図するために、確認フレーズでリクエストを終了します。確認フレーズの例は次のとおりです: ``` "do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?" ``` ## 設定 -設定例: +設定例: ``` [MAIN] is_local = True provider_name = ollama -provider_model = deepseek-r1:1.5b +provider_model = deepseek-r1:32b provider_server_address = 127.0.0.1:11434 agent_name = Friday recover_last_session = False @@ -387,7 +407,7 @@ speak = False listen = False work_dir = /Users/mlg/Documents/ai_folder jarvis_personality = False -languages = en ja +languages = en zh [BROWSER] headless_browser = False stealth_mode = False @@ -395,112 +415,154 @@ stealth_mode = False **説明**: -- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。 -- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)。 -- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。 -- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。 -- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます。 -- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。 -- save_session -> セッションデータを保存する(True)か、しない(False)。 -- speak -> 音声出力を有効にする(True)か、しない(False)。 -- listen -> 音声入力を有効にする(True)か、しない(False)。 -- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。 -- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)か、しない(False)。これは単にプロンプトファイルを変更するだけです。 -- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。 -- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。 -- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded. +- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)か。 + +- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか) + +- provider_model -> 使用するモデル、例:deepseek-r1:32b。 + +- provider_server_address -> サーバーアドレス、例:ローカルの場合は127.0.0.1:11434。非ローカルAPIの場合は何でも設定します。 + +- agent_name -> エージェントの名前、例:Friday。TTSのトリガーワードとして使用されます。 + +- recover_last_session -> 前回のセッションから再開する(True)かしない(False)か。 + +- save_session -> セッションデータを保存する(True)かしない(False)か。 + +- speak -> 音声出力を有効にする(True)かしない(False)か。 + +- listen -> 音声入力をリッスンする(True)かしない(False)か。 + +- work_dir -> AIがアクセスできるフォルダ。例:/Users/user/Documents/。 + +- jarvis_personality -> JARVISのような性格を使用する(True)かしない(False)か。これは単にプロンプトファイルを変更します。 + +- languages -> サポートされている言語のリスト。LLMルーターが正しく機能するために必要です。あまりにも多くの言語や類似した言語を入れすぎないようにしてください。 + +- headless_browser -> 表示ウィンドウなしでブラウザを実行する(True)かしない(False)か。 + +- stealth_mode -> ボット検出を困難にします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。 + +- languages -> サポートされている言語のリスト。エージェントルーティングシステムに必要です。言語リストが長いほど、ダウンロードされるモデルが多くなります。 ## プロバイダー -以下の表は利用可能なプロバイダーを示しています: +以下の表は、利用可能なプロバイダーを示しています: -| プロバイダー | ローカル? | 説明 | +| プロバイダー | ローカル? | 説明 | |-----------|--------|-----------------------------------------------------------| -| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 | -| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 | -| lm-studio | はい | LM studio(`lm-studio`)を使用してローカルでLLMを実行 | -| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 | -| deepseek-api | いいえ | Deepseek API(非プライベート) | -| huggingface| いいえ | Hugging-Face API(非プライベート) | -| togetherAI | いいえ | together AI API(非プライベート)を使用 +| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します | +| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行します | +| lm-studio | はい | LM studioでLLMをローカル実行します(`lm-studio`) | +| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 | +| deepseek-api | いいえ | Deepseek API(非プライベート) | +| huggingface| いいえ | Hugging-Face API(非プライベート) | +| togetherAI | いいえ | together AI APIを使用(非プライベート) | +| google | いいえ | google gemini APIを使用(非プライベート) | - -プロバイダーを選択するには、config.iniを変更します: +プロバイダーを選択するには、config.iniを変更します: ``` -is_local = False -provider_name = openai -provider_model = gpt-4o +is_local = True +provider_name = ollama +provider_model = deepseek-r1:32b provider_server_address = 127.0.0.1:5000 ``` -`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalse。 +`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalseである必要があります。 `provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。 `provider_model`: エージェントが使用するモデルを設定します。 -`provider_server_address`: サーバープロバイダーを使用しない場合は何でも設定できます。 +`provider_server_address`: サーバーアドレス。APIプロバイダーには使用されません。 # 既知の問題 ## Chromedriverの問題 -**既知のエラー#1:** *chromedriverの不一致* +**既知のエラー #1:** *chromedriverの不一致* `Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113 Current browser version is 134.0.6998.89 with binary path` これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。 -最新バージョンをダウンロードするには、次のリンクにアクセスしてください: +最新バージョンをダウンロードするためにナビゲートする必要があります: https://developer.chrome.com/docs/chromedriver/downloads -Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください: +Chromeバージョン115以降を使用している場合は、以下にアクセスしてください: https://googlechromelabs.github.io/chrome-for-testing/ -お使いのOSに対応するchromedriverバージョンをダウンロードします。 +そして、OSに一致するchromedriverバージョンをダウンロードします。 -![alt text](./media/chromedriver_readme.png) +![代替テキスト](./media/chromedriver_readme.png) -このセクションが不完全な場合は、問題を報告してください。 +このセクションが不完全な場合は、問題を提起してください。 + +## 接続アダプタの問題 + +``` +Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions' +``` + +プロバイダーのIPアドレスの前に`http://`があることを確認してください: + +`provider_server_address = http://127.0.0.1:11434` + +## SearxNGのベースURLを指定する必要があります + +``` +raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.") +ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable. +``` + +`.env.example`を`.env`として移動しなかった可能性がありますか?SEARXNG_BASE_URLをエクスポートすることもできます: + +`export SEARXNG_BASE_URL="http://127.0.0.1:8080"` ## FAQ -**Q: どのようなハードウェアが必要ですか?** +**Q: どのようなハードウェアが必要ですか?** -| モデルサイズ | GPU | コメント | -|-----------|--------|-----------------------------------------------------------| -| 7B | 8GB VRAM | ⚠️ 推奨されません。パフォーマンスが低く、頻繁に幻覚を起こし、プランナーエージェントが失敗する可能性が高いです。 | -| 14B | 12GB VRAM (例: RTX 3060) | ✅ 簡単なタスクには使用可能です。ウェブブラウジングや計画タスクには苦労する可能性があります。 | -| 32B | 24GB以上のVRAM (例: RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画にはまだ苦労する可能性があります。 | -| 70B+ | 48GB以上のVRAM (例: Mac Studio) | 💪 優れた性能。高度なユースケースに推奨されます。 | +| モデルサイズ | GPU | コメント | +|-----------|------------|--------------------------------------------------------------------------| +| 7B | 8GB VRAM | ⚠️ 非推奨。パフォーマンスが悪く、幻覚が頻繁に発生し、プランナーエージェントは失敗する可能性が高いです。 | +| 14B | 12GB VRAM(例:RTX 3060) | ✅ 簡単なタスクには使用可能。ウェブブラウジングや計画タスクで苦労する可能性があります。 | +| 32B | 24GB以上のVRAM(例:RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画でまだ苦労する可能性があります。 | +| 70B+ | 48GB以上のVRAM(例:mac studio) | 💪 素晴らしい。高度なユースケースに推奨されます。 | -**Q: なぜ他のモデルではなくDeepseek R1を選ぶのですか?** +**Q: なぜ他のモデルではなくDeepseek R1なのですか?** -Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。 +Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに合っていると考えており、他のモデルも正常に動作しますが、Deepseekが私たちの主要な選択肢です。 -**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?** +**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?** -Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。 +ローカルが実行されていること(`ollama serve`)、`config.ini`がプロバイダーと一致していること、依存関係がインストールされていることを確認してください。それでも解決しない場合は、遠慮なく問題を提起してください。 -**Q: 本当に100%ローカルで実行できますか?** +**Q: 本当に100%ローカルで実行できますか?** -はい、OllamaまたはServerプロバイダーを使用すると、すべての音声認識、LLM、および音声合成モデルがローカルで実行されます。非ローカルオプション(OpenAIまたは他のAPI)はオプションです。 +はい、Ollama、lm-studio、またはサーバープロバイダーを使用すると、すべての音声認識、LLM、テキスト読み上げモデルがローカルで実行されます。非ローカルオプション(OpenAIまたはその他のAPI)はオプションです。 -**Q: Manusを持っているのに、なぜAgenticSeekを使用する必要があるのですか?** +**Q: Manusがあるのに、なぜAgenticSeekを使うべきなのですか?** -これは、AIエージェントに関する興味から始まったサイドプロジェクトです。特別な点は、ローカルモデルを使用し、APIを避けることです。 -私たちは、JarvisやFriday(アイアンマン映画)からインスピレーションを得て、「クール」にしようとしましたが、機能性に関してはManusから多くのインスピレーションを得ています。なぜなら、人々が最初に求めているのはローカルのManusの代替品だからです。 -Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIのコストを回避します。 +これは、AIエージェントへの関心から始めたサイドプロジェクトです。特別なのは、ローカルモデルを使用し、APIを避けたいということです。 +私たちはJarvisとFriday(アイアンマン映画)からインスピレーションを得て「クール」にしましたが、機能性についてはManusからより多くのインスピレーションを得ています。なぜなら、それが人々が最初に望むもの、つまりローカルなManusの代替だからです。 +Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIコストを回避します。 -## 貢献 +## 貢献する -AgenticSeekを改善するための開発者を探しています!オープンな問題やディスカッションを確認してください。 +AgenticSeekを改善するための開発者を募集しています!オープンな問題やディスカッションを確認してください。 -[![Star History Chart](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date) +[貢献ガイド](./docs/CONTRIBUTING.md) -## 著者: - > [Fosowl](https://github.com/Fosowl) - > [steveh8758](https://github.com/steveh8758) +[![スター履歴チャート](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date) + +## メンテナー: + + > [Fosowl](https://github.com/Fosowl) | パリ時間 + + > [https://github.com/antoineVIVIES](antoineVIVIES) | 台北時間 + + > [steveh8758](https://github.com/steveh8758) | 台北時間 |(常に忙しい) \ No newline at end of file From 94fb15359bae2ecc2d17b7b1b1c3df6a73b49ac2 Mon Sep 17 00:00:00 2001 From: rense Date: Tue, 6 May 2025 18:03:14 +0200 Subject: [PATCH 26/29] allow connecting to remote Ollama server --- sources/llm_provider.py | 92 +++++++++++++++++++++++------------------ 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/sources/llm_provider.py b/sources/llm_provider.py index cde9d6d..263ed07 100644 --- a/sources/llm_provider.py +++ b/sources/llm_provider.py @@ -1,27 +1,27 @@ - import os -import time -import ollama -from ollama import chat -import requests -import subprocess -import ipaddress -import httpx -import socket import platform +import socket +import subprocess +import time from urllib.parse import urlparse -from dotenv import load_dotenv, set_key + +import httpx +import requests +from dotenv import load_dotenv +from ollama import Client as OllamaClient from openai import OpenAI -from typing import List, Tuple, Type, Dict -from sources.utility import pretty_print, animate_thinking + from sources.logger import Logger +from sources.utility import pretty_print, animate_thinking + class Provider: - def __init__(self, provider_name, model, server_address = "127.0.0.1:5000", is_local=False): + def __init__(self, provider_name, model, server_address="127.0.0.1:5000", is_local=False): self.provider_name = provider_name.lower() self.model = model self.is_local = is_local self.server_ip = server_address + self.server_address = server_address self.available_providers = { "ollama": self.ollama_fn, "server": self.server_fn, @@ -44,7 +44,7 @@ class Provider: self.api_key = self.get_api_key(self.provider_name) elif self.provider_name != "ollama": pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success") - + def get_model_name(self) -> str: return self.model @@ -57,7 +57,7 @@ class Provider: exit(1) return api_key - def respond(self, history, verbose = True): + def respond(self, history, verbose=True): """ Use the choosen provider to generate text. """ @@ -73,7 +73,8 @@ class Provider: except AttributeError as e: raise NotImplementedError(f"{str(e)}\nIs {self.provider_name} implemented ?") except ModuleNotFoundError as e: - raise ModuleNotFoundError(f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?") + 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." @@ -106,8 +107,7 @@ class Provider: except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e: return False - - def server_fn(self, history, verbose = False): + def server_fn(self, history, verbose=False): """ Use a remote server with LLM to generate text. """ @@ -141,50 +141,59 @@ class Provider: pretty_print(f"An error occurred: {str(e)}", color="failure") break except KeyError as e: - raise Exception(f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e + raise Exception( + f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e except Exception as e: raise e return thought - def ollama_fn(self, history, verbose = False): + def ollama_fn(self, history, verbose=False): """ - Use local ollama server to generate text. + Use local or remote Ollama server to generate text. """ thought = "" + host = "http://localhost:11434" if self.is_local else f"http://{self.server_address}" + client = OllamaClient(host=host) + try: - stream = chat( + stream = client.chat( model=self.model, messages=history, stream=True, ) for chunk in stream: - if verbose: - print(chunk['message']['content'], end='', flush=True) - thought += chunk['message']['content'] + if verbose: + print(chunk["message"]["content"], end="", flush=True) + thought += chunk["message"]["content"] except httpx.ConnectError as e: - raise Exception("\nOllama connection failed. provider should not be set to ollama if server address is not localhost") from e - except ollama.ResponseError as e: - if e.status_code == 404: + raise Exception( + f"\nOllama connection failed at {host}. Check if the server is running." + ) from e + except Exception as e: + if hasattr(e, 'status_code') and e.status_code == 404: animate_thinking(f"Downloading {self.model}...") - ollama.pull(self.model) + client.pull(self.model) self.ollama_fn(history, verbose) if "refused" in str(e).lower(): - raise Exception("Ollama connection failed. is the server running ?") from e + raise Exception( + f"Ollama connection refused at {host}. Is the server running?" + ) from e raise e + return thought - + def huggingface_fn(self, history, verbose=False): """ Use huggingface to generate text. """ from huggingface_hub import InferenceClient client = InferenceClient( - api_key=self.get_api_key("huggingface") + api_key=self.get_api_key("huggingface") ) completion = client.chat.completions.create( - model=self.model, - messages=history, - max_tokens=1024, + model=self.model, + messages=history, + max_tokens=1024, ) thought = completion.choices[0].message return thought.content @@ -212,7 +221,7 @@ class Provider: return thought except Exception as e: raise Exception(f"OpenAI API error: {str(e)}") from e - + def google_fn(self, history, verbose=False): """ Use google gemini to generate text. @@ -278,8 +287,8 @@ class Provider: return thought except Exception as e: raise Exception(f"Deepseek API error: {str(e)}") from e - - def lm_studio_fn(self, history, verbose = False): + + def lm_studio_fn(self, history, verbose=False): """ Use local lm-studio server to generate text. lm studio use endpoint /v1/chat/completions not /chat/completions like openai @@ -304,14 +313,14 @@ class Provider: raise Exception(f"An error occurred: {str(e)}") from e return thought - def dsk_deepseek(self, history, verbose = False): + 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, + DeepSeekAPI, AuthenticationError, RateLimitError, NetworkError, @@ -340,7 +349,7 @@ class Provider: raise APIError(f"API error occurred: {str(e)}") from e return None - def test_fn(self, history, verbose = True): + def test_fn(self, history, verbose=True): """ This function is used to conduct tests. """ @@ -349,6 +358,7 @@ class Provider: """ return thought + if __name__ == "__main__": provider = Provider("server", "deepseek-r1:32b", " x.x.x.x:8080") res = provider.respond(["user", "Hello, how are you?"]) From ca2eea8089e3ab968129fe59d49684dfc423b7d8 Mon Sep 17 00:00:00 2001 From: martin legrand Date: Tue, 6 May 2025 20:23:53 +0200 Subject: [PATCH 27/29] update readme --- README.md | 4 +++- README_CHS.md | 2 ++ README_CHT.md | 2 ++ README_FR.md | 5 +++-- README_JP.md | 4 +++- 5 files changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f2db2de..a226acd 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,9 @@ Disclaimer: This demo, including all the files that appear (e.g: CV_candidates.z ## Installation -Make sure you have chrome driver, docker and python3.10 (or newer) installed. +Make sure you have chrome driver, docker and python3.10 installed. + +We highly advice you use exactly python3.10 for the setup. Dependencies error might happen otherwise. For issues related to chrome driver, see the **Chromedriver** section. diff --git a/README_CHS.md b/README_CHS.md index 5789d2e..92b87bd 100644 --- a/README_CHS.md +++ b/README_CHS.md @@ -34,6 +34,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 确保已安装了 Chrome driver,Docker 和 Python 3.10(或更新)。 +我们强烈建议您使用 Python 3.10 进行设置,否则可能会发生依赖错误。 + 有关于 Chrome driver 的问题,请参见 **Chromedriver** 部分。 ### 1️⃣ **复制储存库与设置环境变数** diff --git a/README_CHT.md b/README_CHT.md index 777f835..fd0f301 100644 --- a/README_CHT.md +++ b/README_CHT.md @@ -36,6 +36,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 確保已安裝了 Chrome driver,Docker 和 Python 3.10(或更新)。 +我们强烈建议您使用 Python 3.10 进行设置,否则可能会发生依赖错误。 + 有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。 ### 1️⃣ **複製儲存庫與設置環境變數** diff --git a/README_FR.md b/README_FR.md index e11b28a..6bb948f 100644 --- a/README_FR.md +++ b/README_FR.md @@ -23,7 +23,6 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 - ## Fonctionnalités: - **100% Local**: Fonctionne en local sur votre PC. Vos données restent les vôtres. @@ -44,7 +43,9 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469 ## **Installation** -Assurez-vous d’avoir installé le pilote Chrome, Docker et Python 3.10 (ou une version plus récente). +Assurez-vous d’avoir installé le pilote Chrome, Docker et Python 3.10. + +Nous vous conseillons fortement d'utiliser exactement Python 3.10 pour l'installation. Des erreurs de dépendances pourraient survenir autrement. Pour les problèmes liés au pilote Chrome, consultez la section Chromedriver. diff --git a/README_JP.md b/README_JP.md index a7e0761..83c0f30 100644 --- a/README_JP.md +++ b/README_JP.md @@ -36,7 +36,9 @@ https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316 ## インストール -Chromeドライバー、Docker、Python3.10(またはそれ以降)がインストールされていることを確認してください。 +Chrome Driver、Docker、Python 3.10がインストールされていることを確認してください。 + +セットアップにはPython 3.10を正確に使用することを強くお勧めします。そうでない場合、依存関係のエラーが発生する可能性があります。 Chromeドライバーに関する問題については、**Chromedriver**セクションを参照してください。 From dd6ddaeca34dbf1d6e1e0a7a61d596331915827a Mon Sep 17 00:00:00 2001 From: martin legrand Date: Tue, 6 May 2025 22:43:51 +0200 Subject: [PATCH 28/29] upd readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a226acd..db4ec52 100644 --- a/README.md +++ b/README.md @@ -122,8 +122,9 @@ Install requirements: `pip3 install -r requirements.txt` ## Setup for running LLM locally on your machine -**We recommend using at the very least Deepseek 14B, smaller models will struggle with tasks especially for web browsing.** +**Hardware Requirements:** +To run LLMs locally, you'll need sufficient hardware. At a minimum, a GPU capable of running Qwen/Deepseek 14B is required. See the FAQ for detailed model/performance recommendations. **Setup your local provider** From ef915029613be39b719d1de333e713f9382c6e2b Mon Sep 17 00:00:00 2001 From: martin legrand Date: Thu, 8 May 2025 13:38:07 +0200 Subject: [PATCH 29/29] fix : connection issue with 0.0.0.0 in app.js + unzoom browser view --- frontend/agentic-seek-front/src/App.js | 8 ++++---- sources/browser.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/frontend/agentic-seek-front/src/App.js b/frontend/agentic-seek-front/src/App.js index a5bcfdc..c4350ea 100644 --- a/frontend/agentic-seek-front/src/App.js +++ b/frontend/agentic-seek-front/src/App.js @@ -25,7 +25,7 @@ function App() { const checkHealth = async () => { try { - await axios.get('http://0.0.0.0:8000/health'); + await axios.get('http://127.0.0.1:8000/health'); setIsOnline(true); console.log('System is online'); } catch { @@ -37,7 +37,7 @@ function App() { const fetchScreenshot = async () => { try { const timestamp = new Date().getTime(); - const res = await axios.get(`http://0.0.0.0:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, { + const res = await axios.get(`http://127.0.0.1:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, { responseType: 'blob' }); console.log('Screenshot fetched successfully'); @@ -76,7 +76,7 @@ function App() { const fetchLatestAnswer = async () => { try { - const res = await axios.get('http://0.0.0.0:8000/latest_answer'); + const res = await axios.get('http://127.0.0.1:8000/latest_answer'); const data = res.data; updateData(data); @@ -134,7 +134,7 @@ function App() { try { console.log('Sending query:', query); setQuery('waiting for response...'); - const res = await axios.post('http://0.0.0.0:8000/query', { + const res = await axios.post('http://127.0.0.1:8000/query', { query, tts_enabled: false }); diff --git a/sources/browser.py b/sources/browser.py index c5f8309..15b6517 100644 --- a/sources/browser.py +++ b/sources/browser.py @@ -616,19 +616,24 @@ class Browser: return self.screenshot_folder + "/updated_screen.png" def screenshot(self, filename:str = 'updated_screen.png') -> bool: - """Take a screenshot of the current page.""" - self.logger.info("Taking screenshot...") + """Take a screenshot of the current page, attempt to capture the full page by zooming out.""" + self.logger.info("Taking full page screenshot...") time.sleep(0.1) try: + original_zoom = self.driver.execute_script("return document.body.style.zoom || 1;") + self.driver.execute_script("document.body.style.zoom='75%'") + time.sleep(0.1) # Allow time for the zoom to take effect path = os.path.join(self.screenshot_folder, filename) if not os.path.exists(self.screenshot_folder): os.makedirs(self.screenshot_folder) self.driver.save_screenshot(path) - self.logger.info(f"Screenshot saved as {filename}") - return True + self.logger.info(f"Full page screenshot saved as {filename}") except Exception as e: - self.logger.error(f"Error taking screenshot: {str(e)}") + self.logger.error(f"Error taking full page screenshot: {str(e)}") return False + finally: + self.driver.execute_script(f"document.body.style.zoom='1'") + return True def apply_web_safety(self): """