Compare commits
46
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 | ||
|
|
ccef61b2b9 | ||
|
|
3cf1cab68f | ||
|
|
0579fd3bb6 | ||
|
|
c6688355a7 | ||
|
|
68ed1834a9 | ||
|
|
487670d207 | ||
|
|
a109ac98ed | ||
|
|
d928a95ed1 | ||
|
|
ffa6873a86 | ||
|
|
db2eb6fbac | ||
|
|
68d471bfc6 | ||
|
|
03c71368f5 | ||
|
|
2fd83289fd | ||
|
|
7dd60a8946 | ||
|
|
ccaf1fae52 | ||
|
|
34e9baccf3 |
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [Fosowl ]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
|
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
||||||
|
|
||||||
### Why AgenticSeek ?
|
### Why AgenticSeek ?
|
||||||
|
|
||||||
@@ -58,20 +58,64 @@ source agentic_seek_env/bin/activate
|
|||||||
|
|
||||||
### 3️⃣ **Install package**
|
### 3️⃣ **Install package**
|
||||||
|
|
||||||
**Automatic Installation:**
|
Ensure Python, Docker and docker compose, and Google chrome are installed.
|
||||||
|
|
||||||
|
We recommand Python 3.10.0.
|
||||||
|
|
||||||
|
**Automatic Installation (Recommanded):**
|
||||||
|
|
||||||
|
For Linux/Macos:
|
||||||
```sh
|
```sh
|
||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
**Manually:**
|
For windows:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip3 install -r requirements.txt
|
./install.bat
|
||||||
# or
|
|
||||||
python3 setup.py install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Manually:**
|
||||||
|
|
||||||
|
**Note: For any OS, ensure the ChromeDriver you install matches your installed Chrome version. Run `google-chrome --version`. See known issues if you have chrome >135**
|
||||||
|
|
||||||
|
- *Linux*:
|
||||||
|
|
||||||
|
Update Package List: `sudo apt update`
|
||||||
|
|
||||||
|
Install Dependencies: `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
|
|
||||||
|
Install ChromeDriver matching your Chrome browser version:
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Macos*:
|
||||||
|
|
||||||
|
Update brew : `brew update`
|
||||||
|
|
||||||
|
Install chromedriver : `brew install --cask chromedriver`
|
||||||
|
|
||||||
|
Install portaudio: `brew install portaudio`
|
||||||
|
|
||||||
|
Upgrade pip : `python3 -m pip install --upgrade pip`
|
||||||
|
|
||||||
|
Upgrade wheel : : `pip3 install --upgrade setuptools wheel`
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Windows*:
|
||||||
|
|
||||||
|
Install pyreadline3 `pip install pyreadline3`
|
||||||
|
|
||||||
|
Install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: `pip install pyaudio`
|
||||||
|
|
||||||
|
Download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
Place chromedriver in a directory included in your PATH.
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Setup for running LLM locally on your machine
|
## Setup for running LLM locally on your machine
|
||||||
@@ -91,26 +135,38 @@ See below for a list of local supported provider.
|
|||||||
|
|
||||||
**Update the config.ini**
|
**Update the config.ini**
|
||||||
|
|
||||||
Change the config.ini file to set the provider_name to a supported provider and provider_model to `deepseek-r1:14b`
|
Change the config.ini file to set the provider_name to a supported provider and provider_model to a LLM supported by your provider. We recommand reasoning model such as *Qwen* or *Deepseek*.
|
||||||
|
|
||||||
NOTE: `deepseek-r1:14b`is an example, use a bigger model if your hardware allow it.
|
See the **FAQ** at the end of the README for required hardware.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # Whenever you are running locally or with remote provider.
|
||||||
provider_name = ollama # or lm-studio, openai, etc..
|
provider_name = ollama # or lm-studio, openai, etc..
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # choose a model that fit your hardware
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # name of your AI
|
||||||
|
recover_last_session = True # whenever to recover the previous session
|
||||||
|
save_session = True # whenever to remember the current session
|
||||||
|
speak = True # text to speech
|
||||||
|
listen = False # Speech to text, only for CLI
|
||||||
|
work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek.
|
||||||
|
jarvis_personality = False # Whenever to use a more "Jarvis" like personality (experimental)
|
||||||
|
languages = en zh # The list of languages, Text to speech will default to the first language on the list
|
||||||
|
[BROWSER]
|
||||||
|
headless_browser = True # Whenever to use headless browser, recommanded only if you use web interface.
|
||||||
|
stealth_mode = True # Use undetected selenium to reduce browser detection
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note: Some provider (eg: lm-studio) require you to have `http://` in front of the IP. For example `http://127.0.0.1:1234`
|
||||||
|
|
||||||
**List of local providers**
|
**List of local providers**
|
||||||
|
|
||||||
| Provider | Local? | Description |
|
| Provider | Local? | Description |
|
||||||
|-----------|--------|-----------------------------------------------------------|
|
|-----------|--------|-----------------------------------------------------------|
|
||||||
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
||||||
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
|
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
|
||||||
| openai | Yes | Use openai compatible API |
|
| openai | Yes | Use openai compatible API (eg: llama.cpp server) |
|
||||||
|
|
||||||
|
|
||||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||||
|
|
||||||
@@ -124,21 +180,34 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
|||||||
|
|
||||||
## Setup to run with an API
|
## Setup to run with an API
|
||||||
|
|
||||||
Set the desired provider in the `config.ini`
|
Set the desired provider in the `config.ini`. See below for a list of API providers.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = False
|
is_local = False
|
||||||
provider_name = openai
|
provider_name = google
|
||||||
provider_model = gpt-4o
|
provider_model = gemini-2.0-flash
|
||||||
provider_server_address = 127.0.0.1:5000
|
provider_server_address = 127.0.0.1:5000 # doesn't matter
|
||||||
```
|
```
|
||||||
|
Warning: Make sure there is not trailing space in the config.
|
||||||
|
|
||||||
WARNING: Make sure there is not trailing space in the config.
|
Export your API key: `export <<PROVIDER>>_API_KEY="xxx"`
|
||||||
|
|
||||||
Set `is_local` to True if using a local openai-based api.
|
Example: export `TOGETHER_API_KEY="xxxxx"`
|
||||||
|
|
||||||
Change the IP address if your openai-based api run on your own server.
|
**List of API providers**
|
||||||
|
|
||||||
|
| Provider | Local? | Description |
|
||||||
|
|-----------|--------|-----------------------------------------------------------|
|
||||||
|
| openai | Depends | Use ChatGPT API |
|
||||||
|
| deepseek-api | No | Deepseek API (non-private) |
|
||||||
|
| huggingface| No | Hugging-Face API (non-private) |
|
||||||
|
| togetherAI | No | Use together AI API (non-private) |
|
||||||
|
| google | No | Use google gemini API (non-private) |
|
||||||
|
|
||||||
|
*We advice against using gpt-4o or other closedAI models*, performance are poor for web browsing and task planning.
|
||||||
|
|
||||||
|
Please also note that coding/bash might fail with gemini, it seem to ignore our prompt for format to respect, which are optimized for deepseek r1.
|
||||||
|
|
||||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||||
|
|
||||||
@@ -171,6 +240,8 @@ start ./start_services.cmd # Window
|
|||||||
python3 cli.py
|
python3 cli.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We advice you set `headless_browser` to False in the config.ini for CLI mode.
|
||||||
|
|
||||||
**Options 2:** Run with the Web interface.
|
**Options 2:** Run with the Web interface.
|
||||||
|
|
||||||
Start the backend.
|
Start the backend.
|
||||||
@@ -185,52 +256,30 @@ Go to `http://localhost:3000/` and you should see the web interface.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 main.py`
|
Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 cli.py` for CLI mode or `python3 api.py` then go to `localhost:3000` for web interface.
|
||||||
|
|
||||||
```sh
|
You can also use speech to text by setting `listen = True` in the config. Only for CLI mode.
|
||||||
sudo ./start_services.sh
|
|
||||||
python3 cli.py
|
|
||||||
```
|
|
||||||
|
|
||||||
You will be prompted with `>>> `
|
To exit, simply say/type `goodbye`.
|
||||||
This indicate AgenticSeek await you type for instructions.
|
|
||||||
You can also use speech to text by setting `listen = True` in the config.
|
|
||||||
|
|
||||||
To exit, simply say `goodbye`.
|
|
||||||
|
|
||||||
Here are some example usage:
|
Here are some example usage:
|
||||||
|
|
||||||
### Coding/Bash
|
> *Make a snake game in python!*
|
||||||
|
|
||||||
> *Make a snake game in python*
|
> *Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.*
|
||||||
|
|
||||||
> *Show me how to multiply matrice in C*
|
> *Write a Go program to calculate the factorial of a number, save it as factorial.go in your workspace*
|
||||||
|
|
||||||
> *Make a blackjack in golang*
|
> *Search my summer_pictures folder for all JPG files, rename them with 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.
|
||||||
@@ -247,9 +296,9 @@ Instead, ask:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **Bonus: Setup to run the LLM on your own server**
|
## **Setup to run the LLM on your own server**
|
||||||
|
|
||||||
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server.
|
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server using our custom llm server.
|
||||||
|
|
||||||
On your "server" that will run the AI model, get the ip address
|
On your "server" that will run the AI model, get the ip address
|
||||||
|
|
||||||
@@ -303,6 +352,8 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
|||||||
|
|
||||||
## Speech to Text
|
## Speech to Text
|
||||||
|
|
||||||
|
Please note that currently speech to text only work in english.
|
||||||
|
|
||||||
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
|
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -393,6 +444,7 @@ The table below show the available providers:
|
|||||||
| deepseek-api | No | Deepseek API (non-private) |
|
| deepseek-api | No | Deepseek API (non-private) |
|
||||||
| huggingface| No | Hugging-Face API (non-private) |
|
| huggingface| No | Hugging-Face API (non-private) |
|
||||||
| togetherAI | No | Use together AI API (non-private) |
|
| togetherAI | No | Use together AI API (non-private) |
|
||||||
|
| google | No | Use google gemini API (non-private) |
|
||||||
|
|
||||||
To select a provider change the config.ini:
|
To select a provider change the config.ini:
|
||||||
|
|
||||||
@@ -435,6 +487,27 @@ And download the chromedriver version matching your OS.
|
|||||||
|
|
||||||
If this section is incomplete please raise an issue.
|
If this section is incomplete please raise an issue.
|
||||||
|
|
||||||
|
## connection adapters Issues
|
||||||
|
|
||||||
|
```
|
||||||
|
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions'
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you have `http://` in front of the provider IP address :
|
||||||
|
|
||||||
|
`provider_server_address = http://127.0.0.1:11434`
|
||||||
|
|
||||||
|
## SearxNG base URL must be provided
|
||||||
|
|
||||||
|
```
|
||||||
|
raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.")
|
||||||
|
ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.
|
||||||
|
```
|
||||||
|
|
||||||
|
Maybe you didn't move `.env.example` as `.env` ? You can also export SEARXNG_BASE_URL:
|
||||||
|
|
||||||
|
`export SEARXNG_BASE_URL="http://127.0.0.1:8080"`
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Q: What hardware do I need?**
|
**Q: What hardware do I need?**
|
||||||
@@ -473,5 +546,9 @@ 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)
|
||||||
+96
-42
@@ -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/m37d7XxZ) [](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`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**本地提供者列表**
|
**本地提供者列表**
|
||||||
|
|
||||||
| 提供者 | 本地? | 描述 |
|
| 提供者 | 本地? | 描述 |
|
||||||
@@ -313,6 +361,8 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
## 语音转文字
|
## 语音转文字
|
||||||
|
|
||||||
|
请注意,目前语音转文字功能仅支持英语。
|
||||||
|
|
||||||
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`:
|
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -469,7 +519,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
|||||||
|
|
||||||
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
|
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
|
||||||
|
|
||||||
**Q:我在执行时 `main.py` 时出现错误。我该怎么办?**
|
**Q:我在执行时 `cli.py` 时出现错误。我该怎么办?**
|
||||||
|
|
||||||
1. 确保 Ollama 正在运行(ollama serve)
|
1. 确保 Ollama 正在运行(ollama serve)
|
||||||
2. 你 `config.ini` 内 `provider_name` 的框架选择正确。
|
2. 你 `config.ini` 内 `provider_name` 的框架选择正确。
|
||||||
@@ -508,6 +558,10 @@ DeepSeek R1 天生会说中文
|
|||||||
|
|
||||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 作者:
|
## 维护者:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙)
|
||||||
|
|
||||||
|
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙)
|
||||||
|
|
||||||
|
> [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙)
|
||||||
|
|||||||
+72
-36
@@ -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/m37d7XxZ) [](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
|
||||||
|
|
||||||
@@ -314,6 +344,8 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
## 語音轉文字
|
## 語音轉文字
|
||||||
|
|
||||||
|
请注意,目前语音转文字功能仅支持英语。
|
||||||
|
|
||||||
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -470,7 +502,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
|||||||
|
|
||||||
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
|
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
|
||||||
|
|
||||||
**Q:我在執行時 `main.py` 時出現錯誤。我該怎麼辦?**
|
**Q:我在執行時 `cli.py` 時出現錯誤。我該怎麼辦?**
|
||||||
|
|
||||||
1. 確保 Ollama 正在運行(ollama serve)
|
1. 確保 Ollama 正在運行(ollama serve)
|
||||||
2. 你 `config.ini` 內 `provider_name` 的框架選擇正確。
|
2. 你 `config.ini` 內 `provider_name` 的框架選擇正確。
|
||||||
@@ -509,6 +541,10 @@ DeepSeek R1 天生会说中文
|
|||||||
|
|
||||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 作者:
|
## 维护者:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙)
|
||||||
|
|
||||||
|
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙)
|
||||||
|
|
||||||
|
> [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙)
|
||||||
+74
-16
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données.
|
Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données.
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||||
|
|
||||||
> 🛠️ **En cours de développement** – On cherche activement des contributeurs!
|
> 🛠️ **En cours de développement** – On cherche activement des contributeurs!
|
||||||
|
|
||||||
@@ -74,9 +74,44 @@ source agentic_seek_env/bin/activate
|
|||||||
|
|
||||||
**Manuel:**
|
**Manuel:**
|
||||||
|
|
||||||
```sh
|
**Note : Pour tous les systèmes d'exploitation, assurez-vous que le ChromeDriver que vous installez correspond à la version de Chrome installée. Exécutez `google-chrome --version`. Consultez les problèmes connus si vous avez Chrome >135**
|
||||||
pip3 install -r requirements.txt
|
|
||||||
```
|
- *Linux*:
|
||||||
|
|
||||||
|
Mettre à jour la liste des paquets : `sudo apt update`
|
||||||
|
|
||||||
|
Installer les dépendances : `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
|
|
||||||
|
Installer ChromeDriver correspondant à la version de votre navigateur Chrome :
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *macOS*:
|
||||||
|
|
||||||
|
Mettre à jour brew : `brew update`
|
||||||
|
|
||||||
|
Installer chromedriver : `brew install --cask chromedriver`
|
||||||
|
|
||||||
|
Installer portaudio : `brew install portaudio`
|
||||||
|
|
||||||
|
Mettre à jour pip : `python3 -m pip install --upgrade pip`
|
||||||
|
|
||||||
|
Mettre à jour wheel : `pip3 install --upgrade setuptools wheel`
|
||||||
|
|
||||||
|
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Windows*:
|
||||||
|
|
||||||
|
Installer pyreadline3 : `pip install pyreadline3`
|
||||||
|
|
||||||
|
Installer portaudio manuellement (par exemple, via vcpkg ou des binaires précompilés) puis exécutez : `pip install pyaudio`
|
||||||
|
|
||||||
|
Télécharger et installer chromedriver manuellement depuis : https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
Placez chromedriver dans un répertoire inclus dans votre PATH.
|
||||||
|
|
||||||
|
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
|
||||||
## Faire fonctionner sur votre machine
|
## Faire fonctionner sur votre machine
|
||||||
@@ -88,18 +123,35 @@ Lancer votre provider local, par exemple avec ollama:
|
|||||||
ollama serve
|
ollama serve
|
||||||
```
|
```
|
||||||
|
|
||||||
Voyez la section **Provider** pour la liste de provideurs disponible.
|
**Configurer le config.ini**
|
||||||
|
|
||||||
Modifiez le fichier config.ini pour définir provider_name sur le nom d'un provideur et provider_model sur le LLM à utiliser.
|
Modifiez le fichier config.ini pour définir provider_name sur un fournisseur supporté et provider_model sur un LLM compatible avec votre fournisseur. Nous recommandons des modèles de raisonnement comme *Qwen* ou *Deepseek*.
|
||||||
|
|
||||||
|
Consultez la section **FAQ** à la fin du README pour connaître le matériel requis.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # Si vous exécutez localement ou avec un fournisseur distant.
|
||||||
provider_name = ollama # ou lm-studio, openai, etc...
|
provider_name = ollama # ou lm-studio, openai, etc..
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # choisissez un modèle adapté à votre matériel
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # nom de votre IA
|
||||||
|
recover_last_session = True # récupérer ou non la session précédente
|
||||||
|
save_session = True # mémoriser ou non la session actuelle
|
||||||
|
speak = True # synthèse vocale
|
||||||
|
listen = False # reconnaissance vocale, uniquement pour CLI
|
||||||
|
work_dir = /Users/mlg/Documents/workspace # L'espace de travail pour AgenticSeek.
|
||||||
|
jarvis_personality = False # Utiliser une personnalité plus "Jarvis", non recommandé avec des petits modèles
|
||||||
|
languages = en fr # Liste des langages, la synthèse vocale utilisera par défaut la première langue de la liste
|
||||||
|
[BROWSER]
|
||||||
|
headless_browser = True # Utiliser ou non le navigateur sans interface graphique, recommandé uniquement avec l'interface web.
|
||||||
|
stealth_mode = True # Utiliser selenium non détectable pour réduire la détection du navigateur
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Remarque : Certains fournisseurs (ex : lm-studio) nécessitent `http://` devant l'adresse IP. Par exemple `http://127.0.0.1:1234`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Liste des provideurs locaux**
|
**Liste des provideurs locaux**
|
||||||
|
|
||||||
| Fournisseur | Local ? | Description |
|
| Fournisseur | Local ? | Description |
|
||||||
@@ -274,22 +326,27 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
||||||
|
|
||||||
## **Exécuter avec une API**
|
## **Exécuter avec une API externe**
|
||||||
|
|
||||||
AVERTISSEMENT : Assurez-vous qu’il n’y a pas d’espace en fin de ligne dans la configuration.
|
AVERTISSEMENT : Assurez-vous qu’il n’y a pas d’espace en fin de ligne dans la configuration.
|
||||||
|
|
||||||
Définissez is_local sur True si vous utilisez une API basée sur OpenAI localement.
|
|
||||||
|
|
||||||
Changez l’adresse IP si votre API basée sur OpenAI fonctionne sur votre propre serveur.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = False
|
is_local = False
|
||||||
provider_name = openai
|
provider_name = openai
|
||||||
provider_model = gpt-4o
|
provider_model = gpt-4o
|
||||||
provider_server_address = 127.0.0.1:5000
|
provider_server_address = 127.0.0.1:5000 # n'importe pas
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Liste de provideurs API**
|
||||||
|
| Fournisseur | Local ? | Description |
|
||||||
|
|--------------|---------|-----------------------------------------------------------|
|
||||||
|
| openai | Non | Utilise l'API ChatGPT |
|
||||||
|
| deepseek-api | Non | API Deepseek (non privé) |
|
||||||
|
| huggingface | Non | API Hugging-Face (non privé) |
|
||||||
|
| togetherAI | Non | Utilise l'API Together AI (non privé) |
|
||||||
|
| google | Non | Utilise l'API Google Gemini (non privé) |
|
||||||
|
|
||||||
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
@@ -434,6 +491,7 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
|
|||||||
|
|
||||||
[Guide du contributeur](./docs/CONTRIBUTING.md)
|
[Guide du contributeur](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Auteurs/Mainteneurs:
|
## Mainteneurs:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
> [Fosowl](https://github.com/Fosowl)
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [steveh8758](https://github.com/steveh8758)
|
||||||
|
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES)
|
||||||
|
|||||||
+14
-4
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。
|
**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||||
|
|
||||||
> 🛠️ **進行中の作業** – 貢献者を探しています!
|
> 🛠️ **進行中の作業** – 貢献者を探しています!
|
||||||
|
|
||||||
@@ -79,6 +79,14 @@ source agentic_seek_env/bin/activate
|
|||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:**
|
||||||
|
|
||||||
|
```
|
||||||
|
pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi
|
||||||
|
pip install unidic-lite
|
||||||
|
python -m unidic download
|
||||||
|
```
|
||||||
|
|
||||||
**手動で:**
|
**手動で:**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -204,11 +212,11 @@ python3 api.py
|
|||||||
|
|
||||||
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
|
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
|
||||||
|
|
||||||
サービスが`./start_services.sh`で起動していることを確認し、`python3 main.py`でagenticSeekを実行します。
|
サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo ./start_services.sh
|
sudo ./start_services.sh
|
||||||
python3 main.py
|
python3 cli.py
|
||||||
```
|
```
|
||||||
|
|
||||||
`>>> `と表示されます
|
`>>> `と表示されます
|
||||||
@@ -320,6 +328,8 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
## 音声認識
|
## 音声認識
|
||||||
|
|
||||||
|
現在、音声認識は英語でのみ動作することに注意してください。
|
||||||
|
|
||||||
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -453,7 +463,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
|||||||
|
|
||||||
Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
|
Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
|
||||||
|
|
||||||
**Q: `main.py`を実行するとエラーが発生します。どうすればよいですか?**
|
**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?**
|
||||||
|
|
||||||
Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
|
Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
+29
-25
@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
|
|||||||
community a harassment-free experience for everyone, regardless of age, body
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
and orientation.
|
identity and orientation.
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
diverse, inclusive, and healthy community.
|
diverse, inclusive, and healthy community.
|
||||||
@@ -22,17 +22,17 @@ community include:
|
|||||||
* Giving and gracefully accepting constructive feedback
|
* Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
overall community
|
community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
advances of any kind
|
any kind
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email
|
* Publishing others' private information, such as a physical or email address,
|
||||||
address, without their explicit permission
|
without their explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
@@ -52,15 +52,15 @@ decisions when appropriate.
|
|||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
an individual is officially representing the community in public spaces.
|
an individual is officially representing the community in public spaces.
|
||||||
Examples of representing our community include using an official e-mail address,
|
Examples of representing our community include using an official email address,
|
||||||
posting via an official social media account, or acting as an appointed
|
posting via an official social media account, or acting as an appointed
|
||||||
representative at an online or offline event.
|
representative at an online or offline event.
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement:
|
||||||
.
|
you need to send a private message to `fossowl` or `mow8758` on discord.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
|
|||||||
|
|
||||||
### 2. Warning
|
### 2. Warning
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
**Community Impact**: A violation through a single incident or series of
|
||||||
of actions.
|
actions.
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
interaction with the people involved, including unsolicited interaction with
|
interaction with the people involved, including unsolicited interaction with
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
like social media. Violating these terms may lead to a temporary or
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
permanent ban.
|
ban.
|
||||||
|
|
||||||
### 3. Temporary Ban
|
### 3. Temporary Ban
|
||||||
|
|
||||||
@@ -109,20 +109,24 @@ Violating these terms may lead to a permanent ban.
|
|||||||
standards, including sustained inappropriate behavior, harassment of an
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
the community.
|
community.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
version 2.0, available at
|
version 2.1, available at
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
Community Impact Guidelines were inspired by
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
https://www.contributor-covenant.org/translations.
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Python 3.8 or higher.
|
- Python 3.10 or higher.
|
||||||
- Docker or Orbstack.
|
- Docker or Orbstack or Podman.
|
||||||
- Ollama with some deepseek-r1 variant installed or similar local reasoning model.
|
- Ollama with some deepseek-r1 variant installed or similar local reasoning model.
|
||||||
- Basic familiarity with Python and AI models.
|
- Basic familiarity with Python and AI models.
|
||||||
- Join the discord (optional): https://discord.gg/v8r4xy8x
|
- Join the discord (optional): https://discord.gg/8hGDaME3TC
|
||||||
|
|
||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
+49
-9
@@ -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()
|
||||||
@@ -98,18 +144,12 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx
|
|||||||
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:
|
||||||
@@ -39,11 +41,16 @@ class Interaction:
|
|||||||
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
@@ -45,6 +45,9 @@ class Provider:
|
|||||||
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()
|
||||||
api_key_var = f"{provider.upper()}_API_KEY"
|
api_key_var = f"{provider.upper()}_API_KEY"
|
||||||
@@ -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",
|
||||||
|
|||||||
+93
-20
@@ -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,22 +31,44 @@ 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."""
|
||||||
return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt"
|
return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt"
|
||||||
@@ -79,6 +101,32 @@ class Memory():
|
|||||||
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."""
|
||||||
if self.session_recovered == True:
|
if self.session_recovered == True:
|
||||||
@@ -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:
|
||||||
|
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()
|
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,25 +220,48 @@ 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__))))
|
||||||
memory = Memory("You are a helpful assistant.",
|
memory = Memory("You are a helpful assistant.",
|
||||||
|
|||||||
@@ -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?"],
|
||||||
|
|||||||
+38
-14
@@ -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)
|
||||||
|
|
||||||
|
if self.language == 'zh':
|
||||||
|
sentence = re.sub(
|
||||||
|
r'[^\u4e00-\u9fff\s,。!?《》【】“”‘’()()—]',
|
||||||
|
'',
|
||||||
|
sentence
|
||||||
|
)
|
||||||
|
else:
|
||||||
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
|
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
|
||||||
sentence = re.sub(r'\b-\w+\b', '', sentence)
|
sentence = re.sub(r'\b-\w+\b', '', sentence)
|
||||||
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
|
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
|
||||||
sentence = re.sub(r'\s+', ' ', sentence).strip()
|
|
||||||
sentence = sentence.replace('.com', '')
|
sentence = sentence.replace('.com', '')
|
||||||
|
|
||||||
|
sentence = re.sub(r'\s+', ' ', sentence).strip()
|
||||||
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:
|
||||||
"""
|
"""
|
||||||
@@ -31,13 +32,45 @@ class FileFinder(Tools):
|
|||||||
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
|
||||||
|
|||||||
+9
-3
@@ -41,13 +41,19 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if Docker Compose is installed
|
# Check if Docker Compose is installed
|
||||||
if ! command_exists docker-compose; then
|
if ! command_exists docker-compose && ! docker compose version >/dev/null 2>&1; then
|
||||||
echo "Error: Docker Compose is not installed. Please install it first."
|
echo "Error: Docker Compose is not installed. Please install it first."
|
||||||
echo "On Ubuntu: sudo apt install docker-compose"
|
echo "On Ubuntu: sudo apt install docker-compose"
|
||||||
echo "Or via pip: pip install docker-compose"
|
echo "Or via pip: pip install docker-compose"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if command_exists docker-compose; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
else
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if docker-compose.yml exists
|
# Check if docker-compose.yml exists
|
||||||
if [ ! -f "docker-compose.yml" ]; then
|
if [ ! -f "docker-compose.yml" ]; then
|
||||||
echo "Error: docker-compose.yml not found in the current directory."
|
echo "Error: docker-compose.yml not found in the current directory."
|
||||||
@@ -60,8 +66,8 @@ sleep 4
|
|||||||
docker stop $(docker ps -a -q)
|
docker stop $(docker ps -a -q)
|
||||||
echo "All containers stopped"
|
echo "All containers stopped"
|
||||||
|
|
||||||
if ! docker-compose up; then
|
if ! $COMPOSE_CMD up; then
|
||||||
echo "Error: Failed to start containers. Check Docker logs with 'docker compose logs'."
|
echo "Error: Failed to start containers. Check Docker logs with '$COMPOSE_CMD logs'."
|
||||||
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
|
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -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