Compare commits
34
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f5736f6b | ||
|
|
101c103aeb | ||
|
|
de315a43a1 | ||
|
|
90f173ba52 | ||
|
|
e4591ea1b4 | ||
|
|
a7deffedec | ||
|
|
5949540007 | ||
|
|
7afb79117b | ||
|
|
442bb4a340 | ||
|
|
99467be133 | ||
|
|
887060acdf | ||
|
|
90609e960c | ||
|
|
d893928221 | ||
|
|
5bc086fd9d | ||
|
|
aca176b9e7 | ||
|
|
9707dbcbf9 | ||
|
|
52e5af8116 | ||
|
|
42058244f2 | ||
|
|
bddaa75e8c | ||
|
|
7904439f35 | ||
|
|
c873af3d00 | ||
|
|
fa2852d3e7 | ||
|
|
1c4ebefae4 | ||
|
|
96a6dd368a | ||
|
|
ed4f04b19c | ||
|
|
f325865869 | ||
|
|
a15dd998f3 | ||
|
|
f17dc0550b | ||
|
|
ed76c8415b | ||
|
|
9f2c105074 | ||
|
|
3cf1cab68f | ||
|
|
0579fd3bb6 | ||
|
|
c6688355a7 | ||
|
|
68ed1834a9 |
@@ -52,12 +52,16 @@ 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**
|
||||||
|
|
||||||
|
Ensure Python, Docker and docker compose, and Google chrome are installed.
|
||||||
|
|
||||||
|
We recommand Python 3.10.0.
|
||||||
|
|
||||||
**Automatic Installation (Recommanded):**
|
**Automatic Installation (Recommanded):**
|
||||||
|
|
||||||
For Linux/Macos:
|
For Linux/Macos:
|
||||||
@@ -66,41 +70,51 @@ For Linux/Macos:
|
|||||||
```
|
```
|
||||||
|
|
||||||
For windows:
|
For windows:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
./install.bat
|
./install.bat
|
||||||
```
|
```
|
||||||
|
|
||||||
**Manually:**
|
**Manually:**
|
||||||
|
|
||||||
First, you need to install these packages:
|
**Note: For any OS, ensure the ChromeDriver you install matches your installed Chrome version. Run `google-chrome --version`. See known issues if you have chrome >135**
|
||||||
|
|
||||||
- *Linux*:
|
- *Linux*:
|
||||||
|
|
||||||
Updates package list (apt-get update).
|
Update Package List: `sudo apt update`
|
||||||
|
|
||||||
Install these:
|
Install Dependencies: `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
alsa-utils, portaudio19-dev, python3-pyaudio, libgtk-3-dev, libnotify-dev, libgconf-2-4, libnss3, libxss1, selenium
|
|
||||||
|
|
||||||
Make sure to install docker + docker-compose if not already.
|
Install ChromeDriver matching your Chrome browser version:
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
- *Macos*:
|
- *Macos*:
|
||||||
|
|
||||||
Update package list.
|
Update brew : `brew update`
|
||||||
Install chromedriver.
|
|
||||||
Install portaudio.
|
Install chromedriver : `brew install --cask chromedriver`
|
||||||
Install chromedriver and selenium.
|
|
||||||
|
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*:
|
- *Windows*:
|
||||||
|
|
||||||
Install pyreadline3, selenium portaudio, pyAudio and chromedriver
|
Install pyreadline3 `pip install pyreadline3`
|
||||||
|
|
||||||
Then install pip requirements:
|
Install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: `pip install pyaudio`
|
||||||
|
|
||||||
```sh
|
Download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started
|
||||||
pip3 install -r requirements.txt
|
|
||||||
# or
|
Place chromedriver in a directory included in your PATH.
|
||||||
python3 setup.py install
|
|
||||||
```
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -121,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)
|
||||||
|
|
||||||
@@ -159,9 +185,9 @@ Set the desired provider in the `config.ini`. See below for a list of API provid
|
|||||||
```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.
|
||||||
|
|
||||||
@@ -179,6 +205,10 @@ Example: export `TOGETHER_API_KEY="xxxxx"`
|
|||||||
| togetherAI | No | Use together AI API (non-private) |
|
| togetherAI | No | Use together AI API (non-private) |
|
||||||
| google | No | Use google gemini 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)
|
||||||
|
|
||||||
*See the **Known issues** section if you are having issues*
|
*See the **Known issues** section if you are having issues*
|
||||||
@@ -210,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.
|
||||||
@@ -232,37 +264,22 @@ To exit, simply say/type `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 today’s date, and save a list of renamed files in photos_list.txt*
|
||||||
|
|
||||||
### Web search
|
> *Search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt.*
|
||||||
|
|
||||||
> *Do a web search to find cool tech startup in Japan working on cutting edge AI research*
|
> *Search the web for the latest AI news articles from 2025, select three, and write a Python script to scrape their titles and summaries. Save the script as news_scraper.py and the summaries in ai_news.txt in /home/projects*
|
||||||
|
|
||||||
> *Can you find on the internet who created AgenticSeek?*
|
> *Friday, search the web for a free stock price API, register with supersuper7434567@gmail.com then write a Python script to fetch using the API daily prices for Tesla, and save the results in stock_prices.csv*
|
||||||
|
|
||||||
> *Can you use a fuel calculator online to estimate the cost of a Nice - Milan trip*
|
*Note that form filling capabilities are still experimental and might fail.*
|
||||||
|
|
||||||
### File system
|
|
||||||
|
|
||||||
> *Hey can you find where is contract.pdf i lost it*
|
|
||||||
|
|
||||||
> *Show me how much space I have left on my disk*
|
|
||||||
|
|
||||||
> *Can you follow the readme and install project at /home/path/project*
|
|
||||||
|
|
||||||
### Casual
|
|
||||||
|
|
||||||
> *Tell me about Rennes, France*
|
|
||||||
|
|
||||||
> *Should I pursue a phd ?*
|
|
||||||
|
|
||||||
> *What's the best workout routine ?*
|
|
||||||
|
|
||||||
|
|
||||||
After you type your query, AgenticSeek will allocate the best agent for the task.
|
After you type your query, AgenticSeek will allocate the best agent for the task.
|
||||||
@@ -470,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?**
|
||||||
@@ -508,5 +546,9 @@ We’re looking for developers to improve AgenticSeek! Check out open issues or
|
|||||||
[](https://www.star-history.com/#Fosowl/agenticSeek&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)
|
||||||
+93
-41
@@ -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 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。
|
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](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`
|
||||||
|
|
||||||
|
从以下网址手动下载并安装 chromedriver:https://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`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**本地提供者列表**
|
**本地提供者列表**
|
||||||
|
|
||||||
| 提供者 | 本地? | 描述 |
|
| 提供者 | 本地? | 描述 |
|
||||||
@@ -510,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) | 台北时间 | (总是很忙)
|
||||||
|
|||||||
+69
-35
@@ -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 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。
|
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](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`
|
||||||
|
|
||||||
|
从以下网址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
将 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
## 在本地機器上運行 AgenticSeek
|
## 在本地機器上運行 AgenticSeek
|
||||||
|
|
||||||
@@ -511,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) | 台北时间 | (总是很忙)
|
||||||
+62
-9
@@ -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 |
|
||||||
@@ -439,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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -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
@@ -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
@@ -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 AgenticSeek’s 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())
|
||||||
@@ -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
@@ -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])
|
||||||
|
|||||||
@@ -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..."
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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__))))
|
||||||
|
|||||||
@@ -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, what’s good? Find my ‘mixtape.mp3’ real quick", "LOW"),
|
("Yo, what’s 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"),
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
@@ -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 = [
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user