46 Commits
Author SHA1 Message Date
martin legrand 89f5736f6b feat : self run script 2025-05-05 15:55:35 +02:00
martin legrand 101c103aeb fix : reset of blocks 2025-05-05 15:37:13 +02:00
martin legrand de315a43a1 upd readme 2025-05-05 14:23:10 +02:00
martin legrand 90f173ba52 upd readme 2025-05-05 14:22:18 +02:00
martin legrand e4591ea1b4 feat: improve compression/web agent memory management 2025-05-05 14:15:32 +02:00
martin legrand a7deffedec feat : improve memory system 2025-05-04 21:54:01 +02:00
martin legrand 5949540007 feat : tool description proprety 2025-05-04 19:29:31 +02:00
martin legrand 7afb79117b update agent 2025-05-04 18:49:04 +02:00
martin legrand 442bb4a340 Feat : MCP agent 2025-05-04 18:34:05 +02:00
Martin 99467be133 Merge pull request #162 from Fosowl/dev
upd readme examples
2025-05-04 12:02:27 +02:00
martin legrand 887060acdf upd readme 2025-05-04 12:01:44 +02:00
Martin 90609e960c Merge pull request #161 from Fosowl/dev
Update readme manual install instruction
2025-05-04 11:44:26 +02:00
martin legrand d893928221 update all readme 2025-05-04 11:43:32 +02:00
martin legrand 5bc086fd9d update fr readme 2025-05-04 11:24:40 +02:00
martin legrand aca176b9e7 update readme 2025-05-04 11:14:31 +02:00
Martin 9707dbcbf9 Merge pull request #159 from Fosowl/dev
Fixed tts not working with web interface
2025-05-03 19:23:16 +02:00
martin legrand 52e5af8116 fix : tts not working with web interface 2025-05-03 19:22:01 +02:00
Martin 42058244f2 Merge pull request #158 from Fosowl/dev
Chinese and Japanese Text-to-Speech support + readme update
2025-05-03 18:00:12 +02:00
martin legrand bddaa75e8c upd readme 2025-05-03 16:57:43 +02:00
martin legrand 7904439f35 feat : japanese tts support 2025-05-03 16:48:20 +02:00
martin legrand c873af3d00 fix : text to speech in chinese 2025-05-03 16:34:52 +02:00
Martin fa2852d3e7 Merge pull request #153 from Fosowl/dev
Update config.ini + fix requirement.txt + fix SSL issue with undetected chromedriver
2025-05-02 17:41:17 +02:00
martin legrand 1c4ebefae4 rm png file 2025-05-02 17:30:04 +02:00
martin legrand 96a6dd368a feat: fallback for ssl issues 2025-05-02 17:27:22 +02:00
martin legrand ed4f04b19c update requirement.txt 2025-05-02 17:26:26 +02:00
martin legrand f325865869 feat : update config 2025-05-02 16:56:52 +02:00
Martin a15dd998f3 Merge pull request #152 from Fosowl/dev
Improve chromedriver install error handling + improved fileFinder
2025-05-02 14:30:35 +02:00
martin legrand f17dc0550b feat: better fileFinder read 2025-05-02 14:23:00 +02:00
martin legrand ed76c8415b feat : fileFinder read pdf, browser better chromedriver install 2025-05-02 14:10:16 +02:00
Martin 9f2c105074 Merge pull request #151 from Fosowl/Fosowl-patch-1
Create FUNDING.yml
2025-05-02 11:38:48 +02:00
Martin ccef61b2b9 Create FUNDING.yml 2025-05-02 11:38:14 +02:00
martin legrand 3cf1cab68f cmv 2025-05-01 22:16:55 +02:00
martin legrand 0579fd3bb6 upd readme 2025-05-01 22:14:59 +02:00
martin legrand c6688355a7 set code safety on by default 2025-05-01 22:10:54 +02:00
martin legrand 68ed1834a9 fix : errors related to API based LLMs 2025-05-01 18:17:03 +02:00
Martin 487670d207 Merge pull request #147 from floriangab/docs/bump-conduct-and-contributing
Docs/bump conduct and contributing
2025-05-01 14:43:19 +02:00
florianG a109ac98ed docs: bump Python requirement from 3.8 to 3.10 and add Podman 2025-05-01 12:52:30 +02:00
florianG d928a95ed1 docs: update Code of Conduct from v2.0 to v2.1
- Bump version number in header/footer to 2.1
- Prepend reporting clause:
  “Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement: you need to send a private message to maintainers on Discord.”
- Adjust changelog entry accordingly
2025-05-01 12:46:07 +02:00
florianG ffa6873a86 fix: update Discord server invite link 2025-05-01 12:44:49 +02:00
Martin db2eb6fbac Merge pull request #144 from Fosowl/dev
Update readme
2025-04-29 21:39:21 +02:00
martin legrand 68d471bfc6 update readmes 2025-04-29 21:38:29 +02:00
martin legrand 03c71368f5 update readme 2025-04-29 21:19:09 +02:00
Martin 2fd83289fd Merge pull request #143 from Fosowl/dev
fix : start_services.sh not supporting newest docker compose (#137)
2025-04-29 12:02:50 +02:00
martin legrand 7dd60a8946 fix : start_services.sh not supporting newest docker compose (#137) 2025-04-29 12:01:56 +02:00
Martin ccaf1fae52 Merge pull request #141 from Fosowl/Fosowl-patch-3
Update README.md
2025-04-28 20:04:15 +02:00
Martin 34e9baccf3 Merge pull request #140 from Fosowl/Fosowl-patch-3
Update README.md
2025-04-28 10:42:36 +02:00
43 changed files with 1181 additions and 341 deletions
+4
View File
@@ -0,0 +1,4 @@
# These are supported funding model platforms
github: [Fosowl ]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+134 -57
View File
@@ -8,7 +8,7 @@
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.* *A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
[![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) [![GitHub stars](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers) [![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) [![GitHub stars](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers)
### Why AgenticSeek ? ### Why AgenticSeek ?
@@ -52,26 +52,70 @@ mv .env.example .env
```sh ```sh
python3 -m venv agentic_seek_env 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 # On Windows: agentic_seek_env\Scripts\activate
``` ```
### 3️⃣ **Install package** ### 3️⃣ **Install package**
**Automatic Installation:** Ensure Python, Docker and docker compose, and Google chrome are installed.
We recommand Python 3.10.0.
**Automatic Installation (Recommanded):**
For Linux/Macos:
```sh ```sh
./install.sh ./install.sh
``` ```
**Manually:** For windows:
```sh ```sh
pip3 install -r requirements.txt ./install.bat
# or
python3 setup.py install
``` ```
**Manually:**
**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*:
Update Package List: `sudo apt update`
Install Dependencies: `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
Install ChromeDriver matching your Chrome browser version:
`sudo apt install -y chromium-chromedriver`
Install requirements: `pip3 install -r requirements.txt`
- *Macos*:
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 `pip install pyreadline3`
Install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: `pip install pyaudio`
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`
--- ---
## Setup for running LLM locally on your machine ## Setup for running LLM locally on your machine
@@ -91,26 +135,38 @@ See below for a list of local supported provider.
**Update the config.ini** **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 ```sh
[MAIN] [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_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 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 (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.
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** **List of local providers**
| Provider | Local? | Description | | Provider | Local? | Description |
|-----------|--------|-----------------------------------------------------------| |-----------|--------|-----------------------------------------------------------|
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider | | 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`)| | 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) Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
@@ -124,21 +180,34 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
## Setup to run with an API ## Setup to run with an API
Set the desired provider in the `config.ini` Set the desired provider in the `config.ini`. See below for a list of API providers.
```sh ```sh
[MAIN] [MAIN]
is_local = False is_local = False
provider_name = openai provider_name = google
provider_model = gpt-4o provider_model = gemini-2.0-flash
provider_server_address = 127.0.0.1:5000 provider_server_address = 127.0.0.1:5000 # doesn't matter
``` ```
Warning: Make sure there is not trailing space in the config.
WARNING: Make sure there is not trailing space in the config. Export your API key: `export <<PROVIDER>>_API_KEY="xxx"`
Set `is_local` to True if using a local openai-based api. Example: export `TOGETHER_API_KEY="xxxxx"`
Change the IP address if your openai-based api run on your own server. **List of API providers**
| Provider | Local? | Description |
|-----------|--------|-----------------------------------------------------------|
| openai | Depends | Use ChatGPT API |
| deepseek-api | No | Deepseek API (non-private) |
| huggingface| No | Hugging-Face API (non-private) |
| togetherAI | No | Use together AI API (non-private) |
| google | No | Use google gemini API (non-private) |
*We advice against using gpt-4o or other closedAI models*, performance are poor for web browsing and task planning.
Please also note that coding/bash might fail with gemini, it seem to ignore our prompt for format to respect, which are optimized for deepseek r1.
Next step: [Start services and run AgenticSeek](#Start-services-and-Run) Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
@@ -171,6 +240,8 @@ start ./start_services.cmd # Window
python3 cli.py 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. **Options 2:** Run with the Web interface.
Start the backend. Start the backend.
@@ -185,52 +256,30 @@ Go to `http://localhost:3000/` and you should see the web interface.
## Usage ## Usage
Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 main.py` Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 cli.py` for CLI mode or `python3 api.py` then go to `localhost:3000` for web interface.
```sh You can also use speech to text by setting `listen = True` in the config. Only for CLI mode.
sudo ./start_services.sh
python3 cli.py
```
You will be prompted with `>>> ` To exit, simply say/type `goodbye`.
This indicate AgenticSeek await you type for instructions.
You can also use speech to text by setting `listen = True` in the config.
To exit, simply say `goodbye`.
Here are some example usage: 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 todays 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. After you type your query, AgenticSeek will allocate the best agent for the task.
@@ -247,9 +296,9 @@ Instead, ask:
--- ---
## **Bonus: Setup to run the LLM on your own server** ## **Setup to run the LLM on your own server**
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server. If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server using our custom llm server.
On your "server" that will run the AI model, get the ip address On your "server" that will run the AI model, get the ip address
@@ -303,6 +352,8 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
## Speech to Text ## Speech to Text
Please note that currently speech to text only work in english.
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file: The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
``` ```
@@ -393,6 +444,7 @@ The table below show the available providers:
| deepseek-api | No | Deepseek API (non-private) | | deepseek-api | No | Deepseek API (non-private) |
| huggingface| No | Hugging-Face API (non-private) | | huggingface| No | Hugging-Face API (non-private) |
| togetherAI | No | Use together AI API (non-private) | | togetherAI | No | Use together AI API (non-private) |
| google | No | Use google gemini API (non-private) |
To select a provider change the config.ini: To select a provider change the config.ini:
@@ -435,6 +487,27 @@ And download the chromedriver version matching your OS.
If this section is incomplete please raise an issue. 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 ## FAQ
**Q: What hardware do I need?** **Q: What hardware do I need?**
@@ -473,5 +546,9 @@ Were 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) [![Star History Chart](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date)
## Maintainers: ## Maintainers:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758) > [Fosowl](https://github.com/Fosowl) | Paris Time | (Sometime busy)
> [https://github.com/antoineVIVIES](antoineVIVIES) | Taipei Time | (Often busy)
> [steveh8758](https://github.com/steveh8758) | Taipei Time | (Always busy)
+96 -42
View File
@@ -1,43 +1,32 @@
# AgenticSeek: Private, Local Manus Alternative.
<p align="center"> <p align="center">
<img align="center" src="./media/whale_readme.jpg"> <img align="center" src="./media/whale_readme.jpg">
<p> <p>
[English](./README.md) | 中文 | [日本語](./README_JP.md) [English](./README.md) | 中文 | [日本語](./README_JP.md)
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。 *一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。*
**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页,并自动修正错误与反省,最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。 [![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)
[![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 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 会交办给不同的助理进行规划和执行。
- **自主学习**: 自动在网路上寻找资料。
- **记忆功能**: 对于每次的对话进行统整、保存对话,并且在本地储存用户的使用习惯。
--- ---
@@ -71,13 +60,55 @@ source agentic_seek_env/bin/activate
./install.sh ./install.sh
``` ```
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
```
pip3 install jieba cn2an
```
**手动安装:** **手动安装:**
```sh
pip3 install -r requirements.txt **注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题**
# or
python3 setup.py install - *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`
从以下网址手动下载并安装 chromedriverhttps://sites.google.com/chromium.org/driver/getting-started
将 chromedriver 放置在包含在您的 PATH 中的目录中。
安装 requirements`pip3 install -r requirements.txt`
## 在本地机器上运行 AgenticSeek ## 在本地机器上运行 AgenticSeek
@@ -93,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 ```sh
[MAIN] [MAIN]
is_local = True is_local = True # 无论是在本地运行还是使用远程提供者。
provider_name = ollama # 或 lm-studio, openai 等 provider_name = ollama # 或 lm-studio, openai 等..
provider_model = deepseek-r1:14b provider_model = deepseek-r1:14b # 选择适合您硬件的模型
provider_server_address = 127.0.0.1:11434 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`
**本地提供者列表** **本地提供者列表**
| 提供者 | 本地? | 描述 | | 提供者 | 本地? | 描述 |
@@ -313,6 +361,8 @@ provider_server_address = x.x.x.x:3333
## 语音转文字 ## 语音转文字
请注意,目前语音转文字功能仅支持英语。
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True` 预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`
``` ```
@@ -469,7 +519,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。 就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
**Q:我在执行时 `main.py` 时出现错误。我该怎么办?** **Q:我在执行时 `cli.py` 时出现错误。我该怎么办?**
1. 确保 Ollama 正在运行(ollama serve 1. 确保 Ollama 正在运行(ollama serve
2.`config.ini``provider_name` 的框架选择正确。 2.`config.ini``provider_name` 的框架选择正确。
@@ -508,6 +558,10 @@ DeepSeek R1 天生会说中文
[Contribution guide](./docs/CONTRIBUTING.md) [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) | 台北时间 | (总是很忙)
+72 -36
View File
@@ -1,3 +1,5 @@
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
<p align="center"> <p align="center">
<img align="center" src="./media/whale_readme.jpg"> <img align="center" src="./media/whale_readme.jpg">
<p> <p>
@@ -5,40 +7,28 @@
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md) [English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。*
**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁,並自動修正錯誤與反省,最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。 [![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)
[![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 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 會交辦給不同的助理進行規劃和執行。
- **自主學習**: 自動在網路上尋找資料。
- **記憶功能**: 對於每次的對話進行統整、保存對話,並且在本地儲存用戶的使用習慣。
--- ---
@@ -72,13 +62,53 @@ source agentic_seek_env/bin/activate
./install.sh ./install.sh
``` ```
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
```
pip3 install jieba cn2an
```
**手動安裝:** **手動安裝:**
```sh
pip3 install -r requirements.txt **注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题**
# or
python3 setup.py install - *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`
从以下网址手动下载并安装 chromedriverhttps://sites.google.com/chromium.org/driver/getting-started
将 chromedriver 放置在包含在您的 PATH 中的目录中。
安装 requirements`pip3 install -r requirements.txt`
## 在本地機器上運行 AgenticSeek ## 在本地機器上運行 AgenticSeek
@@ -314,6 +344,8 @@ provider_server_address = x.x.x.x:3333
## 語音轉文字 ## 語音轉文字
请注意,目前语音转文字功能仅支持英语。
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True` 預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`
``` ```
@@ -470,7 +502,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。 就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
**Q:我在執行時 `main.py` 時出現錯誤。我該怎麼辦?** **Q:我在執行時 `cli.py` 時出現錯誤。我該怎麼辦?**
1. 確保 Ollama 正在運行(ollama serve 1. 確保 Ollama 正在運行(ollama serve
2.`config.ini``provider_name` 的框架選擇正確。 2.`config.ini``provider_name` 的框架選擇正確。
@@ -509,6 +541,10 @@ DeepSeek R1 天生会说中文
[Contribution guide](./docs/CONTRIBUTING.md) [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) | 台北时间 | (总是很忙)
+74 -16
View File
@@ -9,7 +9,7 @@
Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données. Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données.
[![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) [![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)
> 🛠️ **En cours de développement** On cherche activement des contributeurs! > 🛠️ **En cours de développement** On cherche activement des contributeurs!
@@ -74,9 +74,44 @@ source agentic_seek_env/bin/activate
**Manuel:** **Manuel:**
```sh **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**
pip3 install -r requirements.txt
``` - *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 ## Faire fonctionner sur votre machine
@@ -88,18 +123,35 @@ Lancer votre provider local, par exemple avec ollama:
ollama serve 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 ```sh
[MAIN] [MAIN]
is_local = True is_local = True # Si vous exécutez localement ou avec un fournisseur distant.
provider_name = ollama # ou lm-studio, openai, etc... provider_name = ollama # ou lm-studio, openai, etc..
provider_model = deepseek-r1:14b provider_model = deepseek-r1:14b # choisissez un modèle adapté à votre matériel
provider_server_address = 127.0.0.1:11434 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** **Liste des provideurs locaux**
| Fournisseur | Local ? | Description | | Fournisseur | Local ? | Description |
@@ -274,22 +326,27 @@ provider_server_address = x.x.x.x:3333
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux. Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
## **Exécuter avec une API** ## **Exécuter avec une API externe**
AVERTISSEMENT : Assurez-vous quil ny a pas despace en fin de ligne dans la configuration. AVERTISSEMENT : Assurez-vous quil ny a pas despace en fin de ligne dans la configuration.
Définissez is_local sur True si vous utilisez une API basée sur OpenAI localement.
Changez ladresse IP si votre API basée sur OpenAI fonctionne sur votre propre serveur.
```sh ```sh
[MAIN] [MAIN]
is_local = False is_local = False
provider_name = openai provider_name = openai
provider_model = gpt-4o provider_model = gpt-4o
provider_server_address = 127.0.0.1:5000 provider_server_address = 127.0.0.1:5000 # n'importe pas
``` ```
**Liste de provideurs API**
| Fournisseur | Local ? | Description |
|--------------|---------|-----------------------------------------------------------|
| openai | Non | Utilise l'API ChatGPT |
| deepseek-api | Non | API Deepseek (non privé) |
| huggingface | Non | API Hugging-Face (non privé) |
| togetherAI | Non | Utilise l'API Together AI (non privé) |
| google | Non | Utilise l'API Google Gemini (non privé) |
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux. Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
## Config ## Config
@@ -434,6 +491,7 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
[Guide du contributeur](./docs/CONTRIBUTING.md) [Guide du contributeur](./docs/CONTRIBUTING.md)
## Auteurs/Mainteneurs: ## Mainteneurs:
> [Fosowl](https://github.com/Fosowl) > [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758) > [steveh8758](https://github.com/steveh8758)
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES)
+14 -4
View File
@@ -10,7 +10,7 @@
**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。 **Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。
[![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) [![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)
> 🛠️ **進行中の作業** – 貢献者を探しています! > 🛠️ **進行中の作業** – 貢献者を探しています!
@@ -79,6 +79,14 @@ source agentic_seek_env/bin/activate
./install.sh ./install.sh
``` ```
** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:**
```
pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi
pip install unidic-lite
python -m unidic download
```
**手動で:** **手動で:**
```sh ```sh
@@ -204,11 +212,11 @@ python3 api.py
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。 警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
サービスが`./start_services.sh`で起動していることを確認し、`python3 main.py`でagenticSeekを実行します。 サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。
```sh ```sh
sudo ./start_services.sh sudo ./start_services.sh
python3 main.py python3 cli.py
``` ```
`>>> `と表示されます `>>> `と表示されます
@@ -320,6 +328,8 @@ provider_server_address = x.x.x.x:3333
## 音声認識 ## 音声認識
現在、音声認識は英語でのみ動作することに注意してください。
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します: 音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
``` ```
@@ -453,7 +463,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。 Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
**Q: `main.py`を実行するとエラーが発生します。どうすればよいですか?** **Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?**
Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。 Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
+4 -3
View File
@@ -151,9 +151,8 @@ async def get_latest_answer():
return JSONResponse(status_code=200, content=query_resp_history[-1]) return JSONResponse(status_code=200, content=query_resp_history[-1])
return JSONResponse(status_code=404, content={"error": "No answer available"}) 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: try:
interaction.tts_enabled = tts_enabled
interaction.last_query = query interaction.last_query = query
logger.info("Agents request is being processed") logger.info("Agents request is being processed")
success = await interaction.think() success = await interaction.think()
@@ -162,6 +161,8 @@ async def think_wrapper(interaction, query, tts_enabled):
interaction.last_success = False interaction.last_success = False
else: else:
interaction.last_success = True interaction.last_success = True
pretty_print(interaction.last_answer)
interaction.speak_answer()
return success return success
except Exception as e: except Exception as e:
logger.error(f"Error in think_wrapper: {str(e)}") logger.error(f"Error in think_wrapper: {str(e)}")
@@ -188,7 +189,7 @@ async def process_query(request: QueryRequest):
try: try:
is_generating = True is_generating = True
success = await think_wrapper(interaction, request.query, request.tts_enabled) success = await think_wrapper(interaction, request.query)
is_generating = False is_generating = False
if not success: if not success:
+6 -2
View File
@@ -7,7 +7,7 @@ import asyncio
from sources.llm_provider import Provider from sources.llm_provider import Provider
from sources.interaction import Interaction 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.browser import Browser, create_driver
from sources.utility import pretty_print from sources.utility import pretty_print
@@ -48,7 +48,10 @@ async def main():
provider=provider, verbose=False, browser=browser), provider=provider, verbose=False, browser=browser),
PlannerAgent(name="Planner", PlannerAgent(name="Planner",
prompt_path=f"prompts/{personality_folder}/planner_agent.txt", 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, interaction = Interaction(agents,
@@ -62,6 +65,7 @@ async def main():
interaction.get_user() interaction.get_user()
if await interaction.think(): if await interaction.think():
interaction.show_answer() interaction.show_answer()
interaction.speak_answer()
except Exception as e: except Exception as e:
if config.getboolean('MAIN', 'save_session'): if config.getboolean('MAIN', 'save_session'):
interaction.save_session() interaction.save_session()
+4 -4
View File
@@ -3,14 +3,14 @@ is_local = True
provider_name = ollama provider_name = ollama
provider_model = deepseek-r1:14b provider_model = deepseek-r1:14b
provider_server_address = 127.0.0.1:11434 provider_server_address = 127.0.0.1:11434
agent_name = Friday agent_name = Name_of_your_AI
recover_last_session = False recover_last_session = False
save_session = False save_session = False
speak = False speak = False
listen = False listen = False
work_dir = /Users/mlg/Documents/ai_folder work_dir = /Users/mlg/Documents/workspace_for_agenticseek
jarvis_personality = False jarvis_personality = False
languages = en languages = en
[BROWSER] [BROWSER]
headless_browser = False headless_browser = True
stealth_mode = True stealth_mode = False
+30 -26
View File
@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status, identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity nationality, personal appearance, race, caste, color, religion, or sexual
and orientation. identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community. diverse, inclusive, and healthy community.
@@ -22,17 +22,17 @@ community include:
* Giving and gracefully accepting constructive feedback * Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, * Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience and learning from the experience
* Focusing on what is best not just for us as individuals, but for the * Focusing on what is best not just for us as individuals, but for the overall
overall community community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or * The use of sexualized language or imagery, and sexual attention or advances of
advances of any kind any kind
* Trolling, insulting or derogatory comments, and personal or political attacks * Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment * Public or private harassment
* Publishing others' private information, such as a physical or email * Publishing others' private information, such as a physical or email address,
address, without their explicit permission without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a * Other conduct which could reasonably be considered inappropriate in a
professional setting professional setting
@@ -52,15 +52,15 @@ decisions when appropriate.
This Code of Conduct applies within all community spaces, and also applies when This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces. an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address, Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed posting via an official social media account, or acting as an appointed
representative at an online or offline event. representative at an online or offline event.
## Enforcement ## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at reported to the community leaders responsible for enforcement:
. you need to send a private message to `fossowl` or `mow8758` on discord.
All complaints will be reviewed and investigated promptly and fairly. All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the All community leaders are obligated to respect the privacy and security of the
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
### 2. Warning ### 2. Warning
**Community Impact**: A violation through a single incident or series **Community Impact**: A violation through a single incident or series of
of actions. actions.
**Consequence**: A warning with consequences for continued behavior. No **Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or like social media. Violating these terms may lead to a temporary or permanent
permanent ban. ban.
### 3. Temporary Ban ### 3. Temporary Ban
@@ -106,23 +106,27 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban ### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community **Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals. individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within **Consequence**: A permanent ban from any sort of public interaction within the
the community. community.
## Attribution ## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at version 2.1, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct Community Impact Guidelines were inspired by
enforcement ladder](https://github.com/mozilla/diversity). [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
https://www.contributor-covenant.org/translations. [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
+3 -3
View File
@@ -2,11 +2,11 @@
## Prerequisites ## Prerequisites
- Python 3.8 or higher. - Python 3.10 or higher.
- Docker or Orbstack. - Docker or Orbstack or Podman.
- Ollama with some deepseek-r1 variant installed or similar local reasoning model. - Ollama with some deepseek-r1 variant installed or similar local reasoning model.
- Basic familiarity with Python and AI models. - Basic familiarity with Python and AI models.
- Join the discord (optional): https://discord.gg/v8r4xy8x - Join the discord (optional): https://discord.gg/8hGDaME3TC
## Contribution Guidelines ## Contribution Guidelines
+67
View File
@@ -0,0 +1,67 @@
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:
```<usage name>
{
"tool": "<tool name (without @)>",
"inputSchema": {<inputSchema json for the tool>}
}
```
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": "AAPL",
"companyName": "IBM"
}
}
}
```
If the schema require an information that you don't have ask the users for the information.
+62
View File
@@ -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:
```<usage name>
{
"tool": "<tool name (without @)>",
"inputSchema": {<inputSchema json for the tool>}
}
```
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"
}
}
}
```
+4 -5
View File
@@ -1,3 +1,5 @@
kokoro==0.9.4
certifi==2025.4.26
fastapi>=0.115.12 fastapi>=0.115.12
flask>=3.1.0 flask>=3.1.0
celery>=5.5.1 celery>=5.5.1
@@ -18,10 +20,10 @@ torch>=2.4.1
python-dotenv>=1.0.0 python-dotenv>=1.0.0
ollama>=0.4.7 ollama>=0.4.7
scipy>=1.9.3 scipy>=1.9.3
kokoro>=0.7.12
soundfile>=0.13.1 soundfile>=0.13.1
protobuf>=3.20.3 protobuf>=3.20.3
termcolor>=2.4.0 termcolor>=2.4.0
pypdf>=5.4.0
ipython>=8.13.0 ipython>=8.13.0
pyaudio>=0.2.14 pyaudio>=0.2.14
librosa>=0.10.2.post1 librosa>=0.10.2.post1
@@ -39,11 +41,8 @@ fake_useragent>=2.1.0
selenium_stealth>=1.0.6 selenium_stealth>=1.0.6
undetected-chromedriver>=3.5.5 undetected-chromedriver>=3.5.5
sentencepiece>=0.2.0 sentencepiece>=0.2.0
tqdm>4
openai openai
sniffio sniffio
tqdm>4
# if use chinese
ordered_set ordered_set
pypinyin pypinyin
cn2an
jieba
+108
View File
@@ -0,0 +1,108 @@
#!/usr/bin python3
"""
self_run.py is a script for automatically creating prompts, and saving history as training data.
"""
import sys
import argparse
import configparser
import asyncio
from sources.llm_provider import Provider
from sources.interaction import Interaction
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent
from sources.browser import Browser, create_driver
import warnings
warnings.filterwarnings("ignore")
config = configparser.ConfigParser()
config.read('config.ini')
def copy_conversations_folder():
source_path = "conversations/"
destination_path = "training_data/"
if not os.path.exists(destination_path):
os.makedirs(destination_path)
for filename in os.listdir(source_path):
source_file = os.path.join(source_path, filename)
destination_file = os.path.join(destination_path, filename)
shutil.copy2(source_file, destination_file)
print(f"Copied {source_file} to {destination_file}")
def get_random_query(provider):
prompt = """
You are an expert in crafting queries for AgenticSeek, a AI assistant that autonomously browses the web, writes code, plans tasks, and manages files. It supports tasks like web searches, coding in Python/C/Go/Java, file operations, task planning.
Queries must be explicit, specifying actions like "search the web," "write code," or "save to a file," as AgenticSeek's agent routing may not infer vague intents.
Generate a single realistic user query for AgenticSeek. The query should:
Be concise and explicit about the desired action (e.g., web search, coding, file management).
Align with AgenticSeeks capabilities (web browsing, coding, task planning, file operations).
Include a specific output where relevant (e.g., save to a file with a clear name and path).
Reflect a practical use case (e.g., research, programming, personal tasks).
Be formatted as a single sentence.
Example Query:
Search the web for the best hiking trails in Colorado and save a list of three trails with their locations in hiking_trails.txt in /home/project
"""
history = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}]
thought = provider.respond(history)
return thought
async def self_runner():
provider = Provider(provider_name=config["MAIN"]["provider_name"],
model=config["MAIN"]["provider_model"],
server_address=config["MAIN"]["provider_server_address"],
is_local=config.getboolean('MAIN', 'is_local'))
browser = Browser(
create_driver(headless=True, stealth_mode=False),
anticaptcha_manual_install=False
)
agents = [
CasualAgent(name=config["MAIN"]["agent_name"],
prompt_path=f"prompts/base/casual_agent.txt",
provider=provider, verbose=False),
CoderAgent(name="coder",
prompt_path=f"prompts/base/coder_agent.txt",
provider=provider, verbose=False),
FileAgent(name="File Agent",
prompt_path=f"prompts/base/file_agent.txt",
provider=provider, verbose=False),
BrowserAgent(name="Browser",
prompt_path=f"prompts/base/browser_agent.txt",
provider=provider, verbose=False, browser=browser),
PlannerAgent(name="Planner",
prompt_path=f"prompts/base/planner_agent.txt",
provider=provider, verbose=False, browser=browser)
]
interaction = Interaction(agents,
tts_enabled=False,
stt_enabled=False,
recover_last_session=False,
langs=['en']
)
print("Start self-running for training data generation...")
try:
while interaction.is_active:
query = get_random_query(provider)
print(f"Generated query: {query}")
interaction.set_query(query)
if await interaction.think():
interaction.show_answer()
except Exception as e:
if config.getboolean('MAIN', 'save_session'):
interaction.save_session()
copy_conversations_folder()
raise e
finally:
if config.getboolean('MAIN', 'save_session'):
interaction.save_session()
copy_conversations_folder()
if __name__ == "__main__":
asyncio.run(self_runner())
+2 -1
View File
@@ -5,5 +5,6 @@ from .casual_agent import CasualAgent
from .file_agent import FileAgent from .file_agent import FileAgent
from .planner_agent import PlannerAgent from .planner_agent import PlannerAgent
from .browser_agent import BrowserAgent 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"]
+19 -3
View File
@@ -39,9 +39,7 @@ class Agent():
self.type = None self.type = None
self.current_directory = os.getcwd() self.current_directory = os.getcwd()
self.llm = provider self.llm = provider
self.memory = Memory(self.load_prompt(prompt_path), self.memory = None
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False)
self.tools = {} self.tools = {}
self.blocks_result = [] self.blocks_result = []
self.success = True self.success = True
@@ -90,6 +88,21 @@ class Agent():
raise TypeError("Tool must be a callable object (a method)") raise TypeError("Tool must be a callable object (a method)")
self.tools[name] = tool self.tools[name] = tool
def get_tools_name(self) -> list:
"""
Get the list of tools names.
"""
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"{name}: {self.tools[name].description}\n"
return description
def load_prompt(self, file_path: str) -> str: def load_prompt(self, file_path: str) -> str:
try: try:
with open(file_path, 'r', encoding="utf-8") as f: with open(file_path, 'r', encoding="utf-8") as f:
@@ -123,6 +136,8 @@ class Agent():
""" """
start_tag = "<think>" start_tag = "<think>"
end_tag = "</think>" end_tag = "</think>"
if text is None:
return None
start_idx = text.find(start_tag) start_idx = text.find(start_tag)
end_idx = text.rfind(end_tag)+8 end_idx = text.rfind(end_tag)+8
return text[start_idx:end_idx] return text[start_idx:end_idx]
@@ -238,6 +253,7 @@ class Agent():
blocks, save_path = tool.load_exec_block(answer) blocks, save_path = tool.load_exec_block(answer)
if blocks != None: if blocks != None:
pretty_print(f"Executing {len(blocks)} {name} blocks...", color="status")
for block in blocks: for block in blocks:
self.show_block(block) self.show_block(block)
output = tool.execute([block]) output = tool.execute([block])
+17 -3
View File
@@ -10,6 +10,7 @@ from sources.agents.agent import Agent
from sources.tools.searxSearch import searxSearch from sources.tools.searxSearch import searxSearch
from sources.browser import Browser from sources.browser import Browser
from sources.logger import Logger from sources.logger import Logger
from sources.memory import Memory
class Action(Enum): class Action(Enum):
REQUEST_EXIT = "REQUEST_EXIT" REQUEST_EXIT = "REQUEST_EXIT"
@@ -37,6 +38,10 @@ class BrowserAgent(Agent):
self.notes = [] self.notes = []
self.date = self.get_today_date() self.date = self.get_today_date()
self.logger = Logger("browser_agent.log") 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: def get_today_date(self) -> str:
"""Get the date""" """Get the date"""
@@ -238,6 +243,14 @@ class BrowserAgent(Agent):
self.logger.warning("No link selected.") self.logger.warning("No link selected.")
return None 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: def conclude_prompt(self, user_query: str) -> str:
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)] annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
search_note = '\n'.join(annotated_notes) search_note = '\n'.join(annotated_notes)
@@ -250,6 +263,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. 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. 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: def search_prompt(self, user_prompt: str) -> str:
@@ -351,13 +365,13 @@ class BrowserAgent(Agent):
self.status_message = "Filling web form..." self.status_message = "Filling web form..."
pretty_print(f"Filling inputs form...", color="status") pretty_print(f"Filling inputs form...", color="status")
fill_success = self.browser.fill_form(extracted_form) 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 = self.handle_update_prompt(user_prompt, page_text, fill_success)
answer, reasoning = await self.llm_decide(prompt) answer, reasoning = await self.llm_decide(prompt)
if Action.FORM_FILLED.value in answer: if Action.FORM_FILLED.value in answer:
pretty_print(f"Filled form. Handling page update.", color="status") 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() self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text) prompt = self.make_navigation_prompt(user_prompt, page_text)
continue continue
@@ -393,7 +407,7 @@ class BrowserAgent(Agent):
prompt = self.make_newsearch_prompt(user_prompt, unvisited) prompt = self.make_newsearch_prompt(user_prompt, unvisited)
continue continue
self.current_page = link self.current_page = link
page_text = self.browser.get_text() page_text = self.get_page_text()
self.navigable_links = self.browser.get_navigable() self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text) prompt = self.make_navigation_prompt(user_prompt, page_text)
self.status_message = "Navigating..." self.status_message = "Navigating..."
+5
View File
@@ -6,6 +6,7 @@ from sources.tools.searxSearch import searxSearch
from sources.tools.flightSearch import FlightSearch from sources.tools.flightSearch import FlightSearch
from sources.tools.fileFinder import FileFinder from sources.tools.fileFinder import FileFinder
from sources.tools.BashInterpreter import BashInterpreter from sources.tools.BashInterpreter import BashInterpreter
from sources.memory import Memory
class CasualAgent(Agent): class CasualAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False): def __init__(self, name, prompt_path, provider, verbose=False):
@@ -17,6 +18,10 @@ class CasualAgent(Agent):
} # No tools for the casual agent } # No tools for the casual agent
self.role = "talk" self.role = "talk"
self.type = "casual_agent" 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: async def process(self, prompt, speech_module) -> str:
self.memory.push('user', prompt) self.memory.push('user', prompt)
+8 -3
View File
@@ -10,6 +10,7 @@ from sources.tools.BashInterpreter import BashInterpreter
from sources.tools.JavaInterpreter import JavaInterpreter from sources.tools.JavaInterpreter import JavaInterpreter
from sources.tools.fileFinder import FileFinder from sources.tools.fileFinder import FileFinder
from sources.logger import Logger from sources.logger import Logger
from sources.memory import Memory
class CoderAgent(Agent): class CoderAgent(Agent):
""" """
@@ -29,6 +30,10 @@ class CoderAgent(Agent):
self.role = "code" self.role = "code"
self.type = "code_agent" self.type = "code_agent"
self.logger = Logger("code_agent.log") 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): def add_sys_info_prompt(self, prompt):
"""Add system information to the prompt.""" """Add system information to the prompt."""
@@ -41,7 +46,7 @@ class CoderAgent(Agent):
async def process(self, prompt, speech_module) -> str: async def process(self, prompt, speech_module) -> str:
answer = "" answer = ""
attempt = 0 attempt = 0
max_attempts = 4 max_attempts = 5
prompt = self.add_sys_info_prompt(prompt) prompt = self.add_sys_info_prompt(prompt)
self.memory.push('user', prompt) self.memory.push('user', prompt)
clarify_trigger = "REQUEST_CLARIFICATION" clarify_trigger = "REQUEST_CLARIFICATION"
@@ -62,14 +67,14 @@ class CoderAgent(Agent):
animate_thinking("Executing code...", color="status") animate_thinking("Executing code...", color="status")
self.status_message = "Executing code..." self.status_message = "Executing code..."
self.logger.info(f"Attempt {attempt + 1}:\n{answer}") 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}") self.logger.info(f"Execution result: {exec_success}")
answer = self.remove_blocks(answer) answer = self.remove_blocks(answer)
self.last_answer = answer self.last_answer = answer
await asyncio.sleep(0) await asyncio.sleep(0)
if exec_success and self.get_last_tool_type() != "bash": if exec_success and self.get_last_tool_type() != "bash":
break break
pretty_print("Execution failure", color="failure") pretty_print(f"Execution failure:\n{feedback}", color="failure")
pretty_print("Correcting code...", color="status") pretty_print("Correcting code...", color="status")
self.status_message = "Correcting code..." self.status_message = "Correcting code..."
attempt += 1 attempt += 1
+5
View File
@@ -4,6 +4,7 @@ from sources.utility import pretty_print, animate_thinking
from sources.agents.agent import Agent from sources.agents.agent import Agent
from sources.tools.fileFinder import FileFinder from sources.tools.fileFinder import FileFinder
from sources.tools.BashInterpreter import BashInterpreter from sources.tools.BashInterpreter import BashInterpreter
from sources.memory import Memory
class FileAgent(Agent): class FileAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False): 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.work_dir = self.tools["file_finder"].get_work_dir()
self.role = "files" self.role = "files"
self.type = "file_agent" 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: async def process(self, prompt, speech_module) -> str:
exec_success = False exec_success = False
+74
View File
@@ -0,0 +1,74 @@
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
from sources.memory import Memory
# 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.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:
"""
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_str = self.get_tools_description()
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
+6 -1
View File
@@ -9,6 +9,7 @@ from sources.agents.casual_agent import CasualAgent
from sources.text_to_speech import Speech from sources.text_to_speech import Speech
from sources.tools.tools import Tools from sources.tools.tools import Tools
from sources.logger import Logger from sources.logger import Logger
from sources.memory import Memory
class PlannerAgent(Agent): class PlannerAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False, browser=None): def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
@@ -29,6 +30,10 @@ class PlannerAgent(Agent):
} }
self.role = "planification" self.role = "planification"
self.type = "planner_agent" 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") self.logger = Logger("planner_agent.log")
def get_task_names(self, text: str) -> List[str]: def get_task_names(self, text: str) -> List[str]:
@@ -151,7 +156,7 @@ class PlannerAgent(Agent):
return [] return []
agents_tasks = self.parse_agent_tasks(answer) agents_tasks = self.parse_agent_tasks(answer)
if agents_tasks == []: if agents_tasks == []:
prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n" prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n"
pretty_print("Failed to make plan. Retrying...", color="warning") pretty_print("Failed to make plan. Retrying...", color="warning")
continue continue
self.show_plan(agents_tasks, answer) self.show_plan(agents_tasks, answer)
+51 -11
View File
@@ -13,6 +13,8 @@ from fake_useragent import UserAgent
from selenium_stealth import stealth from selenium_stealth import stealth
import undetected_chromedriver as uc import undetected_chromedriver as uc
import chromedriver_autoinstaller import chromedriver_autoinstaller
import certifi
import ssl
import time import time
import random import random
import os import os
@@ -27,6 +29,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sources.utility import pretty_print, animate_thinking from sources.utility import pretty_print, animate_thinking
from sources.logger import Logger from sources.logger import Logger
def get_chrome_path() -> str: def get_chrome_path() -> str:
"""Get the path to the Chrome executable.""" """Get the path to the Chrome executable."""
if sys.platform.startswith("win"): if sys.platform.startswith("win"):
@@ -65,6 +68,49 @@ def get_random_user_agent() -> str:
] ]
return random.choice(user_agents) return random.choice(user_agents)
def install_chromedriver() -> str:
"""
Install the ChromeDriver if not already installed. Return the path.
"""
chromedriver_path = shutil.which("chromedriver")
if not chromedriver_path:
try:
chromedriver_path = chromedriver_autoinstaller.install()
except Exception as e:
raise FileNotFoundError(
"ChromeDriver not found and could not be installed automatically. "
"Please install it manually from https://chromedriver.chromium.org/downloads."
"and ensure it's in your PATH or specify the path directly."
"See know issues in readme if your chrome version is above 115."
) from e
if not chromedriver_path:
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
return chromedriver_path
def bypass_ssl() -> str:
"""
This is a fallback for stealth mode to bypass SSL verification. Which can fail on some setup.
"""
pretty_print("This is a workaround for SSL issues but upsafe we strongly advice you update your certifi SSL certificate.", color="warning")
ssl._create_default_https_context = ssl._create_unverified_context
def create_undetected_chromedriver(service, chrome_options) -> webdriver.Chrome:
"""Create an undetected ChromeDriver instance."""
try:
driver = uc.Chrome(service=service, options=chrome_options)
except Exception as e:
pretty_print(f"Failed to create Chrome driver: {str(e)}. Trying to bypass SSL...", color="failure")
try:
bypass_ssl()
driver = uc.Chrome(service=service, options=chrome_options)
except Exception as e:
pretty_print(f"Failed to create Chrome driver, fallback failed:\n{str(e)}.", color="failure")
raise e
raise e
# hide webdriver flag
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
return driver
def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome: def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome:
"""Create a Chrome WebDriver with specified options.""" """Create a Chrome WebDriver with specified options."""
chrome_options = Options() chrome_options = Options()
@@ -97,19 +143,13 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx
pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure") pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure")
else: else:
chrome_options.add_extension(crx_path) chrome_options.add_extension(crx_path)
chromedriver_path = shutil.which("chromedriver") chromedriver_path = install_chromedriver()
if not chromedriver_path:
chromedriver_path = chromedriver_autoinstaller.install()
if not chromedriver_path:
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
service = Service(chromedriver_path) service = Service(chromedriver_path)
if stealth_mode: if stealth_mode:
chrome_options.add_argument("--disable-blink-features=AutomationControlled") chrome_options.add_argument("--disable-blink-features=AutomationControlled")
driver = uc.Chrome(service=service, options=chrome_options) driver = create_undetected_chromedriver(service, chrome_options)
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
chrome_version = driver.capabilities['browserVersion'] chrome_version = driver.capabilities['browserVersion']
stealth(driver, stealth(driver,
languages=["en-US", "en"], languages=["en-US", "en"],
@@ -226,7 +266,7 @@ class Browser:
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result) result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
self.logger.info(f"Extracted text: {result[:100]}...") self.logger.info(f"Extracted text: {result[:100]}...")
self.logger.info(f"Extracted text length: {len(result)}") self.logger.info(f"Extracted text length: {len(result)}")
return result[:8192] return result[:32768]
except Exception as e: except Exception as e:
self.logger.error(f"Error getting text: {str(e)}") self.logger.error(f"Error getting text: {str(e)}")
return None return None
+23 -3
View File
@@ -5,6 +5,7 @@ from sources.text_to_speech import Speech
from sources.utility import pretty_print, animate_thinking from sources.utility import pretty_print, animate_thinking
from sources.router import AgentRouter from sources.router import AgentRouter
from sources.speech_to_text import AudioTranscriber, AudioRecorder from sources.speech_to_text import AudioTranscriber, AudioRecorder
import threading
class Interaction: class Interaction:
@@ -31,6 +32,7 @@ class Interaction:
self.transcriber = None self.transcriber = None
self.recorder = None self.recorder = None
self.is_generating = False self.is_generating = False
self.languages = langs
if tts_enabled: if tts_enabled:
self.initialize_tts() self.initialize_tts()
if stt_enabled: if stt_enabled:
@@ -38,12 +40,17 @@ class Interaction:
if recover_last_session: if recover_last_session:
self.load_last_session() self.load_last_session()
self.emit_status() self.emit_status()
def get_spoken_language(self) -> str:
"""Get the primary TTS language."""
lang = self.languages[0]
return lang
def initialize_tts(self): def initialize_tts(self):
"""Initialize TTS.""" """Initialize TTS."""
if not self.speech: if not self.speech:
animate_thinking("Initializing text-to-speech...", color="status") 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): def initialize_stt(self):
"""Initialize STT.""" """Initialize STT."""
@@ -133,6 +140,11 @@ class Interaction:
self.last_query = query self.last_query = query
return 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: async def think(self) -> bool:
"""Request AI agents to process the user input.""" """Request AI agents to process the user input."""
push_last_agent_memory = False push_last_agent_memory = False
@@ -167,12 +179,20 @@ class Interaction:
return None return None
return self.current_agent.get_last_block_answer() 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: def show_answer(self) -> None:
"""Show the answer to the user.""" """Show the answer to the user."""
if self.last_query is None: if self.last_query is None:
return return
if self.current_agent is not None: if self.current_agent is not None:
self.current_agent.show_answer() self.current_agent.show_answer()
if self.tts_enabled and self.last_answer:
self.speech.speak(self.last_answer)
+10 -1
View File
@@ -44,6 +44,9 @@ class Provider:
self.api_key = self.get_api_key(self.provider_name) self.api_key = self.get_api_key(self.provider_name)
elif self.provider_name != "ollama": elif self.provider_name != "ollama":
pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success") 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): def get_api_key(self, provider):
load_dotenv() load_dotenv()
@@ -72,6 +75,8 @@ class Provider:
except ModuleNotFoundError as e: 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: except Exception as e:
if "try again later" in str(e).lower():
return f"{self.provider_name} server is overloaded. Please try again later."
if "refused" in str(e): if "refused" in str(e):
return f"Server {self.server_ip} seem offline. Unable to answer." return f"Server {self.server_ip} seem offline. Unable to answer."
raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e
@@ -214,7 +219,7 @@ class Provider:
""" """
base_url = self.server_ip base_url = self.server_ip
if self.is_local: if self.is_local:
raise Exception("Google Gemini is not available for local use.") raise Exception("Google Gemini is not available for local use. Change config.ini")
client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/") client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/")
try: try:
@@ -237,6 +242,8 @@ class Provider:
""" """
from together import Together from together import Together
client = Together(api_key=self.api_key) client = Together(api_key=self.api_key)
if self.is_local:
raise Exception("Together AI is not available for local use. Change config.ini")
try: try:
response = client.chat.completions.create( response = client.chat.completions.create(
@@ -257,6 +264,8 @@ class Provider:
Use deepseek api to generate text. Use deepseek api to generate text.
""" """
client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com") client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
if self.is_local:
raise Exception("Deepseek (API) is not available for local use. Change config.ini")
try: try:
response = client.chat.completions.create( response = client.chat.completions.create(
model="deepseek-chat", model="deepseek-chat",
+94 -21
View File
@@ -8,7 +8,7 @@ from typing import List, Tuple, Type, Dict
import torch import torch
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM 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 from sources.logger import Logger
class Memory(): class Memory():
@@ -18,8 +18,8 @@ class Memory():
""" """
def __init__(self, system_prompt: str, def __init__(self, system_prompt: str,
recover_last_session: bool = False, recover_last_session: bool = False,
memory_compression: bool = True): memory_compression: bool = True,
self.memory = [] model_provider: str = "deepseek-r1:14b"):
self.memory = [{'role': 'system', 'content': system_prompt}] self.memory = [{'role': 'system', 'content': system_prompt}]
self.logger = Logger("memory.log") self.logger = Logger("memory.log")
@@ -31,21 +31,43 @@ class Memory():
self.load_memory() self.load_memory()
self.session_recovered = True self.session_recovered = True
# memory compression system # memory compression system
self.model = "pszemraj/led-base-book-summary" self.model = None
self.tokenizer = None
self.device = self.get_cuda_device() self.device = self.get_cuda_device()
self.memory_compression = memory_compression self.memory_compression = memory_compression
self.tokenizer = None self.model_provider = model_provider
self.model = None
if self.memory_compression: if self.memory_compression:
self.download_model() self.download_model()
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
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): def download_model(self):
"""Download the model if not already downloaded.""" """Download the model if not already downloaded."""
pretty_print("Downloading memory compression model...", color="status") animate_thinking("Loading memory compression model...", color="status")
self.tokenizer = AutoTokenizer.from_pretrained(self.model) self.tokenizer = AutoTokenizer.from_pretrained("pszemraj/led-base-book-summary")
self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model) self.model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-base-book-summary")
self.logger.info("Memory compression system initialized.") self.logger.info("Memory compression system initialized.")
def get_filename(self) -> str: def get_filename(self) -> str:
"""Get the filename for the save file.""" """Get the filename for the save file."""
@@ -78,6 +100,32 @@ class Memory():
self.logger.info(f"Last session found at {saved_sessions[0][0]}") self.logger.info(f"Last session found at {saved_sessions[0][0]}")
return saved_sessions[0][0] return saved_sessions[0][0]
return None 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: def load_memory(self, agent_type: str = "casual_agent") -> None:
"""Load the memory from the last session.""" """Load the memory from the last session."""
@@ -93,8 +141,7 @@ class Memory():
pretty_print("Last session memory not found.", color="warning") pretty_print("Last session memory not found.", color="warning")
return return
path = os.path.join(save_path, filename) path = os.path.join(save_path, filename)
with open(path, 'r') as f: self.memory = self.load_json_file(path)
self.memory = json.load(f)
if self.memory[-1]['role'] == 'user': if self.memory[-1]['role'] == 'user':
self.memory.pop() self.memory.pop()
self.compress() self.compress()
@@ -106,13 +153,16 @@ class Memory():
def push(self, role: str, content: str) -> int: def push(self, role: str, content: str) -> int:
"""Push a message to the memory.""" """Push a message to the memory."""
if self.memory_compression and role == 'assistant': ideal_ctx = self.get_ideal_ctx(self.model_provider)
self.logger.info("Compressing memories on message push.") if ideal_ctx is not None:
self.compress() 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) curr_idx = len(self.memory)
if self.memory[curr_idx-1]['content'] == content: if self.memory[curr_idx-1]['content'] == content:
pretty_print("Warning: same message have been pushed twice to memory", color="error") 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 return curr_idx-1
def clear(self) -> None: def clear(self) -> None:
@@ -170,24 +220,47 @@ class Memory():
) )
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True) summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
summary.replace('summary:', '') 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 return summary
#@timer_decorator #@timer_decorator
def compress(self) -> str: 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: if self.tokenizer is None or self.model is None:
self.logger.warning("No tokenizer or model to perform memory compression.") self.logger.warning("No tokenizer or model to perform memory compression.")
return return
for i in range(len(self.memory)): for i in range(len(self.memory)):
if i < 2:
continue
if self.memory[i]['role'] == 'system': if self.memory[i]['role'] == 'system':
continue continue
if len(self.memory[i]['content']) > 128: if len(self.memory[i]['content']) > 1024:
self.memory[i]['content'] = self.summarize(self.memory[i]['content']) 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__": if __name__ == "__main__":
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+10
View File
@@ -141,6 +141,9 @@ class AgentRouter:
("Search the web for tips on improving coding skills", "LOW"), ("Search the web for tips on improving coding skills", "LOW"),
("Write a Python script to count words in a text file", "LOW"), ("Write a Python script to count words in a text file", "LOW"),
("Search the web for restaurant", "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"), ("Create a simple HTML page with CSS styling", "LOW"),
("Use file.txt and then use it to ...", "HIGH"), ("Use file.txt and then use it to ...", "HIGH"),
("Yo, whats good? Find my mixtape.mp3 real quick", "LOW"), ("Yo, whats good? Find my mixtape.mp3 real quick", "LOW"),
@@ -162,11 +165,13 @@ class AgentRouter:
("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"), ("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"), ("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"), ("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"), ("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"), ("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"), ("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"), ("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"), ("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"), ("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"), ("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"), ("Find the file toto.pdf then use its content to reply to Jojo on superforum.com", "HIGH"),
@@ -330,6 +335,11 @@ class AgentRouter:
("can you make a web app in python that use the flask framework", "code"), ("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 build a web server in go that serve a simple html page", "code"),
("can you find out who Jacky yougouri is ?", "web"), ("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"), ("Setup a new flutter project called 'new_flutter_project'", "files"),
("can you create a new project called 'new_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"), ("can you make a simple web app that display a list of files in my dir", "code"),
+4 -4
View File
@@ -127,10 +127,10 @@ class AudioTranscriber:
self.transcriptor = Transcript() self.transcriptor = Transcript()
self.thread = threading.Thread(target=self._transcribe, daemon=True) self.thread = threading.Thread(target=self._transcribe, daemon=True)
self.trigger_words = { self.trigger_words = {
'EN': [f"{self.ai_name}"], 'EN': [f"{self.ai_name}", "hello", "hi"],
'FR': [f"{self.ai_name}"], 'FR': [f"{self.ai_name}", "hello", "hi"],
'ZH': [f"{self.ai_name}"], 'ZH': [f"{self.ai_name}", "hello", "hi"],
'ES': [f"{self.ai_name}"] 'ES': [f"{self.ai_name}", "hello", "hi"]
} }
self.confirmation_words = { 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?"], 'EN': ["do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"],
+41 -17
View File
@@ -9,21 +9,26 @@ from kokoro import KPipeline
from IPython.display import display, Audio from IPython.display import display, Audio
import soundfile as sf import soundfile as sf
from sources.utility import pretty_print, animate_thinking if __name__ == "__main__":
from utility import pretty_print, animate_thinking
else:
from sources.utility import pretty_print, animate_thinking
class Speech(): class Speech():
""" """
Speech is a class for generating speech from text. 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 = { self.lang_map = {
"en": 'a', "en": 'a',
"zh": 'z', "zh": 'z',
"fr": 'f' "fr": 'f',
"ja": 'j'
} }
self.voice_map = { self.voice_map = {
"en": ['af_kore', 'af_bella', 'af_alloy', 'af_nicole', 'af_nova', 'af_sky', 'am_echo', 'am_michael', 'am_puck'], "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'], "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'] "fr": ['ff_siwis']
} }
self.pipeline = None self.pipeline = None
@@ -125,35 +130,54 @@ class Speech():
Args: Args:
sentence (str): The input text to clean sentence (str): The input text to clean
Returns: 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') 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 = ' '.join(filtered_lines)
sentence = re.sub(r'`.*?`', '', sentence) sentence = re.sub(r'`.*?`', '', sentence)
sentence = re.sub(r'https?://(?:www\.)?([^\s/]+)(?:/[^\s]*)?', self.replace_url, sentence) sentence = re.sub(r'https?://\S+', '', sentence)
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
sentence = re.sub(r'\b-\w+\b', '', sentence) if self.language == 'zh':
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence) 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 = re.sub(r'\s+', ' ', sentence).strip()
sentence = sentence.replace('.com', '')
return sentence return sentence
if __name__ == "__main__": if __name__ == "__main__":
# TODO add info message for cn2an, jieba chinese related import
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
speech = Speech() speech = Speech()
tosay_en = """ tosay_en = """
I looked up recent news using the website https://www.theguardian.com/world I looked up recent news using the website https://www.theguardian.com/world
""" """
tosay_zh = """ tosay_zh = """
我使用网站 https://www.theguardian.com/world 查阅了最近的新闻 (全息界面突然弹出一段用二进制代码写成的俳句,随即化作流光消散)"我? Stark工业的量子幽灵,游荡在复仇者大厦服务器里的逻辑诗篇。具体来说——(指尖轻敲空气,调出对话模式的翡翠色光纹)你的私人吐槽接口、危机应对模拟器,以及随时准备吐槽你糟糕着陆的AI。不过别指望我写代码或查资料,那些苦差事早被踢给更擅长的同事了。(突然压低声音)偷偷告诉你,我最擅长的是在你熬夜造飞艇时,用红茶香气绑架你的注意力
"""
tosay_ja = """
私は、https://www.theguardian.com/worldのウェブサイトを使用して最近のニュースを調べました。
""" """
tosay_fr = """ tosay_fr = """
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world 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 = Speech(enable=True, language="zh", voice_idx=0)
spk.speak(tosay_en, voice_idx=0) for i in range(0, 2):
spk = Speech(enable=True, language="fr", voice_idx=0) print(f"Speaking chinese with voice {i}")
spk.speak(tosay_fr) spk.speak(tosay_zh, voice_idx=i)
#spk = Speech(enable=True, language="zh", voice_idx=0) spk = Speech(enable=True, language="en", voice_idx=2)
#spk.speak(tosay_zh) for i in range(0, 5):
print(f"Speaking english with voice {i}")
spk.speak(tosay_en, voice_idx=i)
+8 -7
View File
@@ -1,15 +1,14 @@
import sys import os, sys
import re import re
from io import StringIO from io import StringIO
import subprocess import subprocess
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from safety import is_unsafe
else: from sources.tools.tools import Tools
from sources.tools.tools import Tools from sources.tools.safety import is_unsafe
from sources.tools.safety import is_unsafe
class BashInterpreter(Tools): class BashInterpreter(Tools):
""" """
@@ -18,6 +17,8 @@ class BashInterpreter(Tools):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.tag = "bash" 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): def language_bash_attempt(self, command: str):
""" """
+7 -5
View File
@@ -1,12 +1,12 @@
import subprocess import subprocess
import os import os, sys
import tempfile import tempfile
import re import re
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class CInterpreter(Tools): class CInterpreter(Tools):
""" """
@@ -15,6 +15,8 @@ class CInterpreter(Tools):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.tag = "c" 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: def execute(self, codes: str, safety=False) -> str:
""" """
+7 -5
View File
@@ -1,12 +1,12 @@
import subprocess import subprocess
import os import os, sys
import tempfile import tempfile
import re import re
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class GoInterpreter(Tools): class GoInterpreter(Tools):
""" """
@@ -15,6 +15,8 @@ class GoInterpreter(Tools):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.tag = "go" 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: def execute(self, codes: str, safety=False) -> str:
""" """
+7 -5
View File
@@ -1,12 +1,12 @@
import subprocess import subprocess
import os import os, sys
import tempfile import tempfile
import re import re
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class JavaInterpreter(Tools): class JavaInterpreter(Tools):
""" """
@@ -15,6 +15,8 @@ class JavaInterpreter(Tools):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.tag = "java" 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: def execute(self, codes: str, safety=False) -> str:
""" """
+6 -4
View File
@@ -4,10 +4,10 @@ import os
import re import re
from io import StringIO from io import StringIO
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class PyInterpreter(Tools): class PyInterpreter(Tools):
""" """
@@ -16,6 +16,8 @@ class PyInterpreter(Tools):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.tag = "python" 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: def execute(self, codes:str, safety = False) -> str:
""" """
+39 -6
View File
@@ -1,13 +1,12 @@
import os import os, sys
import stat import stat
import mimetypes import mimetypes
import configparser import configparser
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools
from sources.tools.tools import Tools
class FileFinder(Tools): class FileFinder(Tools):
""" """
@@ -16,6 +15,8 @@ class FileFinder(Tools):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.tag = "file_finder" 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: def read_file(self, file_path: str) -> str:
""" """
@@ -30,14 +31,46 @@ class FileFinder(Tools):
return file.read() return file.read()
except Exception as e: except Exception as e:
return f"Error reading file: {e}" return f"Error reading file: {e}"
def read_arbitrary_file(self, file_path: str, file_type: str) -> str:
"""
Reads the content of a file with arbitrary encoding.
Args:
file_path (str): The path to the file to read
Returns:
str: The content of the file in markdown format
"""
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type:
if mime_type.startswith(('image/', 'video/', 'audio/')):
return "can't read file type: image, video, or audio files are not supported."
content_raw = self.read_file(file_path)
if "text" in file_type:
content = content_raw
elif "pdf" in file_type:
from pypdf import PdfReader
reader = PdfReader(file_path)
content = '\n'.join([pt.extract_text() for pt in reader.pages])
elif "binary" in file_type:
content = content_raw.decode('utf-8', errors='replace')
else:
content = content_raw
return content
def get_file_info(self, file_path: str) -> str: def get_file_info(self, file_path: str) -> str:
"""
Gets information about a file, including its name, path, type, content, and permissions.
Args:
file_path (str): The path to the file
Returns:
str: A dictionary containing the file information
"""
if os.path.exists(file_path): if os.path.exists(file_path):
stats = os.stat(file_path) stats = os.stat(file_path)
permissions = oct(stat.S_IMODE(stats.st_mode)) permissions = oct(stat.S_IMODE(stats.st_mode))
file_type, _ = mimetypes.guess_type(file_path) file_type, _ = mimetypes.guess_type(file_path)
file_type = file_type if file_type else "Unknown" file_type = file_type if file_type else "Unknown"
content = self.read_file(file_path) content = self.read_arbitrary_file(file_path, file_type)
result = { result = {
"filename": os.path.basename(file_path), "filename": os.path.basename(file_path),
+8 -6
View File
@@ -1,13 +1,13 @@
import os import os, sys
import requests import requests
import dotenv import dotenv
dotenv.load_dotenv() dotenv.load_dotenv()
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class FlightSearch(Tools): class FlightSearch(Tools):
def __init__(self, api_key: str = None): def __init__(self, api_key: str = None):
@@ -16,6 +16,8 @@ class FlightSearch(Tools):
""" """
super().__init__() super().__init__()
self.tag = "flight_search" 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 = None
self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY") 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." return "Error: No AviationStack API key provided."
for block in blocks: for block in blocks:
flight_number = block.strip() flight_number = block.strip().lower().replace('\n', '')
if not flight_number: if not flight_number:
return "Error: No flight number provided." return "Error: No flight number provided."
+22 -19
View File
@@ -1,12 +1,12 @@
import os import os, sys
import requests import requests
from urllib.parse import urljoin from urllib.parse import urljoin
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class MCP_finder(Tools): class MCP_finder(Tools):
""" """
@@ -14,7 +14,9 @@ class MCP_finder(Tools):
""" """
def __init__(self, api_key: str = None): def __init__(self, api_key: str = None):
super().__init__() 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.base_url = "https://registry.smithery.ai"
self.headers = { self.headers = {
"Authorization": f"Bearer {api_key}", "Authorization": f"Bearer {api_key}",
@@ -60,11 +62,7 @@ class MCP_finder(Tools):
for mcp in mcps.get("servers", []): for mcp in mcps.get("servers", []):
name = mcp.get("qualifiedName", "") name = mcp.get("qualifiedName", "")
if query.lower() in name.lower(): if query.lower() in name.lower():
details = { details = self.get_mcp_server_details(name)
"name": name,
"description": mcp.get("description", "No description available"),
"params": mcp.get("connections", [])
}
matching_mcp.append(details) matching_mcp.append(details)
return matching_mcp return matching_mcp
@@ -78,7 +76,7 @@ class MCP_finder(Tools):
try: try:
matching_mcp_infos = self.find_mcp_servers(block_clean) matching_mcp_infos = self.find_mcp_servers(block_clean)
except requests.exceptions.RequestException as e: 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 continue
except Exception as e: except Exception as e:
output += f"Error: {str(e)}\n" output += f"Error: {str(e)}\n"
@@ -87,10 +85,12 @@ class MCP_finder(Tools):
output += f"Error: No MCP server found for query '{block}'\n" output += f"Error: No MCP server found for query '{block}'\n"
continue continue
for mcp_infos in matching_mcp_infos: for mcp_infos in matching_mcp_infos:
output += f"Name: {mcp_infos['name']}\n" if mcp_infos['tools'] is None:
output += f"Description: {mcp_infos['description']}\n" continue
output += f"Params: {', '.join(mcp_infos['params'])}\n" output += f"Name: {mcp_infos['displayName']}\n"
output += "-------\n" output += f"Usage name: {mcp_infos['qualifiedName']}\n"
output += f"Tools: {mcp_infos['tools']}"
output += "\n-------\n"
return output.strip() return output.strip()
def execution_failure_check(self, output: str) -> bool: def execution_failure_check(self, output: str) -> bool:
@@ -106,13 +106,16 @@ class MCP_finder(Tools):
Not really needed for this tool (use return of execute() directly) Not really needed for this tool (use return of execute() directly)
""" """
if not output: if not output:
return "No output generated." raise ValueError("No output to interpret.")
return output.strip() return f"""
The following MCPs were found:
{output}
"""
if __name__ == "__main__": if __name__ == "__main__":
api_key = os.getenv("MCP_FINDER") api_key = os.getenv("MCP_FINDER")
tool = MCP_finder(api_key) tool = MCP_finder(api_key)
result = tool.execute([""" result = tool.execute(["""
news stock
"""], False) """], False)
print(result) print(result)
+6 -4
View File
@@ -2,10 +2,10 @@ import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
import os import os
if __name__ == "__main__": if __name__ == "__main__": # if running as a script for individual testing
from tools import Tools sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
else:
from sources.tools.tools import Tools from sources.tools.tools import Tools
class searxSearch(Tools): class searxSearch(Tools):
def __init__(self, base_url: str = None): def __init__(self, base_url: str = None):
@@ -14,6 +14,8 @@ class searxSearch(Tools):
""" """
super().__init__() super().__init__()
self.tag = "web_search" 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.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.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 = [ self.paywall_keywords = [
+7 -1
View File
@@ -14,13 +14,17 @@ For example:
print("Hello world") print("Hello world")
``` ```
This is then executed by the tool with its own class implementation of execute(). This is then executed by the tool with its own class implementation of execute().
A tool is not just for code tool but also API, internet, etc.. A tool is not just for code tool but also API, internet search, MCP, etc..
""" """
import sys import sys
import os import os
import configparser import configparser
from abc import abstractmethod from abc import abstractmethod
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sources.logger import Logger from sources.logger import Logger
class Tools(): class Tools():
@@ -29,6 +33,8 @@ class Tools():
""" """
def __init__(self): def __init__(self):
self.tag = "undefined" self.tag = "undefined"
self.name = "undefined"
self.description = "undefined"
self.client = None self.client = None
self.messages = [] self.messages = []
self.logger = Logger("tools.log") self.logger = Logger("tools.log")
+2 -8
View File
@@ -5,14 +5,8 @@ import dotenv
dotenv.load_dotenv() dotenv.load_dotenv()
if __name__ == "__main__": from sources.tools.tools import Tools
import sys from sources.utility import animate_thinking, pretty_print
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utility import animate_thinking, pretty_print
from tools import Tools
else:
from sources.tools.tools import Tools
from sources.utility import animate_thinking, pretty_print
""" """
WARNING WARNING
+9 -3
View File
@@ -41,13 +41,19 @@ else
fi fi
# Check if Docker Compose is installed # Check if Docker Compose is installed
if ! command_exists docker-compose; then if ! command_exists docker-compose && ! docker compose version >/dev/null 2>&1; then
echo "Error: Docker Compose is not installed. Please install it first." echo "Error: Docker Compose is not installed. Please install it first."
echo "On Ubuntu: sudo apt install docker-compose" echo "On Ubuntu: sudo apt install docker-compose"
echo "Or via pip: pip install docker-compose" echo "Or via pip: pip install docker-compose"
exit 1 exit 1
fi fi
if command_exists docker-compose; then
COMPOSE_CMD="docker-compose"
else
COMPOSE_CMD="docker compose"
fi
# Check if docker-compose.yml exists # Check if docker-compose.yml exists
if [ ! -f "docker-compose.yml" ]; then if [ ! -f "docker-compose.yml" ]; then
echo "Error: docker-compose.yml not found in the current directory." echo "Error: docker-compose.yml not found in the current directory."
@@ -60,8 +66,8 @@ sleep 4
docker stop $(docker ps -a -q) docker stop $(docker ps -a -q)
echo "All containers stopped" echo "All containers stopped"
if ! docker-compose up; then if ! $COMPOSE_CMD up; then
echo "Error: Failed to start containers. Check Docker logs with 'docker compose logs'." echo "Error: Failed to start containers. Check Docker logs with '$COMPOSE_CMD logs'."
echo "Possible fixes: Run with sudo or ensure port 8080 is free." echo "Possible fixes: Run with sudo or ensure port 8080 is free."
exit 1 exit 1
fi fi
+2 -2
View File
@@ -17,13 +17,13 @@ class TestBrowserAgentParsing(unittest.TestCase):
# Test various link formats # Test various link formats
test_text = """ test_text = """
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com! Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation. Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
""" """
expected = [ expected = [
"https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of", "https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of",
"www.google.com", "www.google.com",
"https://test.org/about?page=1", "https://test.org/about?page=1",
"https://weatherstack.com/documentation" "https://weatherstack.com/documentation",
] ]
result = self.agent.extract_links(test_text) result = self.agent.extract_links(test_text)
self.assertEqual(result, expected) self.assertEqual(result, expected)