Compare commits
74
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41b5772ff0 | ||
|
|
c1a1e9409d | ||
|
|
41fe95fcb1 | ||
|
|
c8bccc2395 | ||
|
|
e45fa86cda | ||
|
|
c8df9e759c | ||
|
|
b3672c60c0 | ||
|
|
97460ded48 | ||
|
|
9a34ff3646 | ||
|
|
a53842b8b7 | ||
|
|
0a8d898e0b | ||
|
|
92f721886c | ||
|
|
ee6687df85 | ||
|
|
8d15546771 | ||
|
|
7ad084b27f | ||
|
|
63cd5eddd7 | ||
|
|
6d053cc3a5 | ||
|
|
27b4aaa5e9 | ||
|
|
3c19f26792 | ||
|
|
12eec50e1c | ||
|
|
cd78cb36a0 | ||
|
|
ec8cab2d6b | ||
|
|
b1f9375115 | ||
|
|
758faf6285 | ||
|
|
20457a574a | ||
|
|
d375359582 | ||
|
|
f738fc732e | ||
|
|
3be07e3dcb | ||
|
|
7d252ad422 | ||
|
|
1c73ef141c | ||
|
|
e3757f54ac | ||
|
|
12afae7472 | ||
|
|
9cbf62b47d | ||
|
|
51b0ca54c7 | ||
|
|
0f116cc3d0 | ||
|
|
a19ef5df66 | ||
|
|
195b4a07a9 | ||
|
|
e3d01083d9 | ||
|
|
102dc60efb | ||
|
|
ca2b05b35e | ||
|
|
713c01193f | ||
|
|
50a9cb8d27 | ||
|
|
bd26d7233d | ||
|
|
45fbf5a88c | ||
|
|
384d9a8c0b | ||
|
|
95d5aea1d5 | ||
|
|
38b1e17628 | ||
|
|
637ca0f826 | ||
|
|
201b3de15c | ||
|
|
4739a1377c | ||
|
|
4bb7a21604 | ||
|
|
725a3c3292 | ||
|
|
e72072090e | ||
|
|
ef91502961 | ||
|
|
2048af854f | ||
|
|
dd6ddaeca3 | ||
|
|
47fec1914a | ||
|
|
9ff69d1876 | ||
|
|
ca2eea8089 | ||
|
|
3678c091ac | ||
|
|
94fb15359b | ||
|
|
3d1b3d02d9 | ||
|
|
c769e790bc | ||
|
|
23a51e6a05 | ||
|
|
94eada9d5d | ||
|
|
2cdbb49ecd | ||
|
|
dd033d4084 | ||
|
|
de2650c007 | ||
|
|
deb79b81ca | ||
|
|
24dc1e1a2c | ||
|
|
af7619650a | ||
|
|
df645f9a02 | ||
|
|
dc6eef8031 | ||
|
|
5c391dbb6e |
@@ -0,0 +1,19 @@
|
||||
# Python cache files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
.venv/
|
||||
|
||||
# Environment variables (secrets)
|
||||
.env
|
||||
|
||||
# Git metadata
|
||||
.git/
|
||||
|
||||
# macOS Finder files
|
||||
.DS_Store
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
@@ -1,3 +1,4 @@
|
||||
SEARXNG_BASE_URL="http://127.0.0.1:8080"
|
||||
OPENAI_API_KEY='xxxxx'
|
||||
DEEPSEEK_API_KEY='xxxxx'
|
||||
OPENROUTER_API_KEY='xxxxx'
|
||||
@@ -9,6 +9,7 @@ test_agent.py
|
||||
config.ini
|
||||
.voices/
|
||||
experimental/
|
||||
chrome_bundle/
|
||||
.logs/
|
||||
.screenshots/*.png
|
||||
.screenshots/*.jpg
|
||||
|
||||
@@ -32,11 +32,15 @@ https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316
|
||||
|
||||
Disclaimer: This demo, including all the files that appear (e.g: CV_candidates.zip), are entirely fictional. We are not a corporation, we seek open-source contributors not candidates.
|
||||
|
||||
> 🛠️ **Work in Progress** – Looking for contributors!
|
||||
> 🛠⚠️️ **Active Work in Progress** – Please note that Code/Bash is not dockerized yet but will be soon (see docker_deployement branch) - Do not deploy over network or production.
|
||||
|
||||
> 🙏 This project started as a side-project with zero roadmap and zero funding. It's grown way beyond what I expected by ending in GitHub Trending. Contributions, feedback, and patience are deeply appreciated.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you have chrome driver, docker and python3.10 (or newer) installed.
|
||||
Make sure you have chrome driver, docker and python3.10 installed.
|
||||
|
||||
We highly advice you use exactly python3.10 for the setup. Dependencies error might happen otherwise.
|
||||
|
||||
For issues related to chrome driver, see the **Chromedriver** section.
|
||||
|
||||
@@ -120,8 +124,9 @@ Install requirements: `pip3 install -r requirements.txt`
|
||||
|
||||
## Setup for running LLM locally on your machine
|
||||
|
||||
**We recommend using at the very least Deepseek 14B, smaller models will struggle with tasks especially for web browsing.**
|
||||
**Hardware Requirements:**
|
||||
|
||||
To run LLMs locally, you'll need sufficient hardware. At a minimum, a GPU capable of running Qwen/Deepseek 14B is required. See the FAQ for detailed model/performance recommendations.
|
||||
|
||||
**Setup your local provider**
|
||||
|
||||
@@ -158,6 +163,8 @@ headless_browser = True # Whenever to use headless browser, recommanded only if
|
||||
stealth_mode = True # Use undetected selenium to reduce browser detection
|
||||
```
|
||||
|
||||
Warning: Do *NOT* set provider_name to `openai` if using LM-studio for running LLMs. Set it to `lm-studio`.
|
||||
|
||||
Note: Some provider (eg: lm-studio) require you to have `http://` in front of the IP. For example `http://127.0.0.1:1234`
|
||||
|
||||
**List of local providers**
|
||||
@@ -200,7 +207,7 @@ Example: export `TOGETHER_API_KEY="xxxxx"`
|
||||
| Provider | Local? | Description |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| openai | Depends | Use ChatGPT API |
|
||||
| deepseek-api | No | Deepseek API (non-private) |
|
||||
| deepseek | 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) |
|
||||
@@ -314,7 +321,7 @@ Clone the repository and enter the `server/`folder.
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
cd agenticSeek/llm_server/
|
||||
```
|
||||
|
||||
Install server specific requirements:
|
||||
@@ -547,8 +554,8 @@ We’re looking for developers to improve AgenticSeek! Check out open issues or
|
||||
|
||||
## Maintainers:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | Paris Time | (Sometime busy)
|
||||
> [Fosowl](https://github.com/Fosowl) | Paris Time
|
||||
|
||||
> [https://github.com/antoineVIVIES](antoineVIVIES) | Taipei Time | (Often busy)
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | Taipei Time | (Always busy)
|
||||
> [steveh8758](https://github.com/steveh8758) | Taipei Time
|
||||
|
||||
+7
-3
@@ -34,6 +34,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
确保已安装了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
||||
|
||||
我们强烈建议您使用 Python 3.10 进行设置,否则可能会发生依赖错误。
|
||||
|
||||
有关于 Chrome driver 的问题,请参见 **Chromedriver** 部分。
|
||||
|
||||
### 1️⃣ **复制储存库与设置环境变数**
|
||||
@@ -149,6 +151,8 @@ headless_browser = True # 是否使用无头浏览器,只有在使用网页界
|
||||
stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测
|
||||
```
|
||||
|
||||
警告:使用 LM-studio 运行 LLM 时,请*不要*将 provider_name 设置为 `openai`。请将其设置为 `lm-studio`。
|
||||
|
||||
注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234`
|
||||
|
||||
|
||||
@@ -560,8 +564,8 @@ DeepSeek R1 天生会说中文
|
||||
|
||||
## 维护者:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙)
|
||||
> [Fosowl](https://github.com/Fosowl) | 巴黎时间
|
||||
|
||||
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙)
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙)
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北时间
|
||||
|
||||
+55
-42
@@ -8,23 +8,23 @@
|
||||
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
|
||||
|
||||
|
||||
*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。*
|
||||
*一个 **100% 本地替代 Manus AI** 的方案,這款支持語音的 AI 助理能够自主瀏覽網頁、编寫代码和規劃任務,同时將所有用戶資料保留在您的裝置上。專門為本地推理模型量身打造,完全在您自己的硬體上執行,确保完全的隐私保护和零雲端依賴。*
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
### 为什么选择 AgenticSeek?
|
||||
### 为什么選擇 AgenticSeek?
|
||||
|
||||
* 🔒 完全本地化与隐私保护 - 所有功能都在您的设备上运行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
|
||||
* 🔒 完全本地化與隐私保护 - 所有功能都在您的设备上運行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
|
||||
|
||||
* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、阅读、提取信息、填写网页表单 — 全程无需人工操作。
|
||||
* 🌐 智能網頁瀏覽 - AgenticSeek 能够自主瀏覽網頁 — 搜索、閱读、提取信息、填寫網页表單 — 全程无需人工操作。
|
||||
|
||||
* 💻 自主编码助手 - 需要代码?它可以编写、调试并运行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
|
||||
* 💻 自主编码助手 - 需要代码?它可以编寫、调试并運行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
|
||||
|
||||
* 🧠 智能代理选择 - 您提问,它会自动选择最适合该任务的代理。就像拥有一个随时待命的专家团队。
|
||||
* 🧠 智能代理选择 - 您提问,它會自动选择最适合该任务的代理。就像拥有一个随时待命的專家团队。
|
||||
|
||||
* 📋 规划与执行复杂任务 - 从旅行规划到复杂项目 — 它能将大型任务分解为步骤,并利用多个 AI 代理完成工作。
|
||||
* 📋 规划與执行复杂任务 - 从旅行规划到复杂项目 — 它能將大型任务分解为步骤,并利用多个 AI 代理完成工作。
|
||||
|
||||
* 🎙️ 语音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,让您能像科幻电影中一样与您的个人 AI 助手对话。
|
||||
* 🎙️ 語音功能 - 清晰、快速、未来感十足的語音與語音轉文本功能,讓您能像科幻电影中一样與您的个人 AI 助手对话。
|
||||
|
||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
@@ -36,6 +36,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
確保已安裝了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
||||
|
||||
我们强烈建议您使用 Python 3.10 進行設定,否则可能會发生依赖错误。
|
||||
|
||||
有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。
|
||||
|
||||
### 1️⃣ **複製儲存庫與設置環境變數**
|
||||
@@ -62,7 +64,7 @@ source agentic_seek_env/bin/activate
|
||||
./install.sh
|
||||
```
|
||||
|
||||
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
|
||||
** 若要將文字轉成語音(TTS)功能支持中文,你需要安装 jieba(中文分詞庫)和 cn2an(中文數字轉換庫):**
|
||||
|
||||
```
|
||||
pip3 install jieba cn2an
|
||||
@@ -71,7 +73,7 @@ pip3 install jieba cn2an
|
||||
**手動安裝:**
|
||||
|
||||
|
||||
**注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题**
|
||||
**注意:對於不同作業系統,請確保已經安装的 ChromeDriver 與您已安装的 Chrome 版本一致。可以執行 `google-chrome --version`。如果您的 Chrome 版本 > 135,請參考已知问题**
|
||||
|
||||
- *Linux*:
|
||||
|
||||
@@ -79,7 +81,7 @@ pip3 install jieba cn2an
|
||||
|
||||
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||
|
||||
安装与您的 Chrome 浏览器版本匹配的 ChromeDriver:
|
||||
安装與您的 Chrome 瀏覽器版本匹配的 ChromeDriver:
|
||||
`sudo apt install -y chromium-chromedriver`
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
@@ -102,11 +104,11 @@ pip3 install jieba cn2an
|
||||
|
||||
安装 pyreadline3:`pip install pyreadline3`
|
||||
|
||||
手动安装 portaudio(例如,通过 vcpkg 或预编译的二进制文件),然后运行:`pip install pyaudio`
|
||||
手动安装 portaudio(例如,通过 vcpkg 或預編譯的二進制文件),然後運行:`pip install pyaudio`
|
||||
|
||||
从以下网址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||
从以下網址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||
|
||||
将 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||
將 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
@@ -114,34 +116,45 @@ pip3 install jieba cn2an
|
||||
|
||||
**建議至少使用 Deepseek 14B 以上參數的模型,較小的模型難以使用助理功能並且很快就會忘記上下文之間的關係。**
|
||||
|
||||
**本地运行助手**
|
||||
**本地運行助手**
|
||||
|
||||
启动你的本地提供者,例如使用 ollama:
|
||||
啟動你的本地提供者,例如使用 ollama:
|
||||
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
请参阅下方支持的本地提供者列表。
|
||||
请参閱下方支持的本地提供者列表。
|
||||
|
||||
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`。
|
||||
修改 config.ini 文件以設定 provider_name 为支持的提供者,并將 provider_model 設定为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen* 或 *Deepseek*。
|
||||
|
||||
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型。
|
||||
请参见 README 末尾的 **FAQ** 部分了解所需硬件。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # 或 lm-studio, openai 等
|
||||
provider_model = deepseek-r1:14b
|
||||
is_local = True # 无论是在本地運行还是使用远程提供者。
|
||||
provider_name = ollama # 或 lm-studio, openai 等..
|
||||
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Jarvis # 您的 AI 助手的名称
|
||||
recover_last_session = True # 是否恢复之前的會话
|
||||
save_session = True # 是否记住当前會话
|
||||
speak = True # 文本轉語音
|
||||
listen = False # 語音轉文本,僅适用于命令行界面
|
||||
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
|
||||
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
|
||||
languages = en zh # 语言列表,文本轉語音將默认使用列表中的第一种语言
|
||||
[BROWSER]
|
||||
headless_browser = True # 是否使用无头瀏覽器,只有在使用網页界面时才推荐使用。
|
||||
stealth_mode = True # 使用无法檢測的 selenium 来减少瀏覽器檢測
|
||||
```
|
||||
|
||||
**本地提供者列表**
|
||||
|
||||
| 提供者 | 本地? | 描述 |
|
||||
|-------------|--------|-------------------------------------------------------|
|
||||
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地运行 LLM |
|
||||
| lm-studio | 是 | 使用 LM Studio 本地运行 LLM(将 `provider_name` 设置为 `lm-studio`)|
|
||||
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地運行 LLM |
|
||||
| lm-studio | 是 | 使用 LM Studio 本地運行 LLM(將 `provider_name` 設定为 `lm-studio`)|
|
||||
| openai | 否 | 使用兼容的 API |
|
||||
|
||||
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
@@ -171,14 +184,14 @@ provider_server_address = 127.0.0.1:5000
|
||||
---
|
||||
|
||||
## Start services and Run
|
||||
(启动服务并运行)
|
||||
(啟動服务并運行)
|
||||
|
||||
如果需要,请激活你的 Python 环境。
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
启动所需的服务。这将启动 `docker-compose.yml` 中的所有服务,包括:
|
||||
啟動所需的服务。这將啟動 `docker-compose.yml` 中的所有服务,包括:
|
||||
- searxng
|
||||
- redis(由 redis 提供支持)
|
||||
- 前端
|
||||
@@ -188,25 +201,25 @@ sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
```
|
||||
|
||||
**选项 1:** 使用 CLI 界面运行。
|
||||
**選項 1:** 使用 CLI 界面運行。
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**选项 2:** 使用 Web 界面运行。
|
||||
**選項 2:** 使用 Web 界面運行。
|
||||
|
||||
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
|
||||
|
||||
启动后端服务。
|
||||
啟動後端服务。
|
||||
|
||||
```sh
|
||||
python3 api.py
|
||||
```
|
||||
|
||||
访问 `http://localhost:3000/`,你应该会看到 Web 界面。
|
||||
访问 `http://localhost:3000/`,你应该會看到 Web 界面。
|
||||
|
||||
请注意,目前 Web 界面不支持消息流式传输。
|
||||
请注意,目前 Web 界面不支持消息流式傳輸。
|
||||
|
||||
|
||||
*如果你不知道如何開始,請參閱 **Usage** 部分*
|
||||
@@ -215,9 +228,9 @@ python3 api.py
|
||||
|
||||
## Usage (使用方法)
|
||||
|
||||
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中设置语言选项。
|
||||
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中設定语言選項。
|
||||
languages = en zh
|
||||
更多信息请参阅 Config 部分
|
||||
更多信息请参閱 Config 部分
|
||||
|
||||
確定所有的核心檔案都啟用了,也就是執行過這條命令 `./start_services.sh` 然後你就可以使用 `python3 cli.py` 來啟動 AgenticSeek 了!
|
||||
|
||||
@@ -274,11 +287,11 @@ python3 cli.py
|
||||
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
|
||||
|
||||
你該說:
|
||||
- 进行网络搜索,找出哪些国家最适合独自旅行
|
||||
- 進行網路搜索,找出哪些国家最适合獨自旅行
|
||||
|
||||
|
||||
而不是說:
|
||||
- 你知道哪些国家适合独自旅行?
|
||||
- 你知道哪些国家适合獨自旅行?
|
||||
|
||||
---
|
||||
|
||||
@@ -344,7 +357,7 @@ provider_server_address = x.x.x.x:3333
|
||||
|
||||
## 語音轉文字
|
||||
|
||||
请注意,目前语音转文字功能仅支持英语。
|
||||
请注意,目前語音轉文字功能僅支援英语。
|
||||
|
||||
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
||||
|
||||
@@ -525,13 +538,13 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
**Q: 是否支持中文以外的语言?**
|
||||
|
||||
DeepSeek R1 天生会说中文
|
||||
DeepSeek R1 天生會说中文
|
||||
|
||||
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
|
||||
|
||||
如果不设置中文?后果可能是:你让它写代码,结果跳出来个"医生代理"(虽然我们根本没有这个代理... 但系统会一脸懵圈!)
|
||||
如果不設定中文?後果可能是:你讓它寫代码,结果跳出来个"醫生代理"(虽然我们根本没有这个代理... 但系统會一脸懵圈!)
|
||||
|
||||
实际上会下载一个小型翻译模型来协助任务分配
|
||||
实际上會下载一个小型翻译模型来协助任务分配
|
||||
|
||||
## 貢獻
|
||||
|
||||
@@ -543,8 +556,8 @@ DeepSeek R1 天生会说中文
|
||||
|
||||
## 维护者:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙)
|
||||
> [Fosowl](https://github.com/Fosowl) | 巴黎時間 | (有时很忙)
|
||||
|
||||
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙)
|
||||
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北時間 | (經常很忙)
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙)
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北時間 | (總是很忙)
|
||||
|
||||
+4
-3
@@ -23,7 +23,6 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
|
||||
|
||||
|
||||
## Fonctionnalités:
|
||||
|
||||
- **100% Local**: Fonctionne en local sur votre PC. Vos données restent les vôtres.
|
||||
@@ -44,7 +43,9 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
## **Installation**
|
||||
|
||||
Assurez-vous d’avoir installé le pilote Chrome, Docker et Python 3.10 (ou une version plus récente).
|
||||
Assurez-vous d’avoir installé le pilote Chrome, Docker et Python 3.10.
|
||||
|
||||
Nous vous conseillons fortement d'utiliser exactement Python 3.10 pour l'installation. Des erreurs de dépendances pourraient survenir autrement.
|
||||
|
||||
Pour les problèmes liés au pilote Chrome, consultez la section Chromedriver.
|
||||
|
||||
@@ -494,4 +495,4 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
|
||||
## Mainteneurs:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES)
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
+298
-216
@@ -1,61 +1,48 @@
|
||||
# AgenticSeek: プライベートなローカルManus代替
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek ロゴ">
|
||||
<p>
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語
|
||||
|
||||
# AgenticSeek: Deepseek R1エージェントによって動作するManusのようなAI。
|
||||
*Manus AIの**100%ローカルな代替**となるこの音声対応AIアシスタントは、自律的にウェブを閲覧し、コードを書き、タスクを計画しながら、すべてのデータをあなたのデバイスに保持します。ローカル推論モデルに合わせて調整されており、完全にあなたのハードウェア上で動作するため、完全なプライバシーとクラウドへの依存ゼロを保証します。*
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
||||
|
||||
**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。
|
||||
### なぜAgenticSeekなのか?
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
* 🔒 完全ローカル&プライベート - すべてがあなたのマシン上で実行されます — クラウドなし、データ共有なし。あなたのファイル、会話、検索はプライベートに保たれます。
|
||||
|
||||
> 🛠️ **進行中の作業** – 貢献者を探しています!
|
||||
* 🌐 スマートなウェブブラウジング - AgenticSeekは自分でインターネットを閲覧できます — 検索、読み取り、情報抽出、ウェブフォーム入力 — すべてハンズフリーで。
|
||||
|
||||
* 💻 自律型コーディングアシスタント - コードが必要ですか?Python、C、Go、Javaなどでプログラムを書き、デバッグし、実行できます — すべて監視なしで。
|
||||
|
||||
* 🧠 スマートエージェント選択 - あなたが尋ねると、タスクに最適なエージェントを自動的に見つけ出します。まるで専門家チームが助けてくれるようです。
|
||||
|
||||
* 📋 複雑なタスクの計画と実行 - 旅行計画から複雑なプロジェクトまで — 大きなタスクをステップに分割し、複数のAIエージェントを使って物事を成し遂げることができます。
|
||||
|
||||
https://github.com/user-attachments/assets/fe9e8006-0462-4793-8b31-25bd42c6d1eb
|
||||
* 🎙️ 音声対応 - クリーンで高速、未来的な音声と音声認識により、まるでSF映画のパーソナルAIのように話しかけることができます。
|
||||
|
||||
### **デモ**
|
||||
|
||||
> *agenticSeekプロジェクトを検索し、必要なスキルを学び、その後CV_candidates.zipを開いて、プロジェクトに最も適した候補者を教えてください。*
|
||||
|
||||
https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316
|
||||
|
||||
*そしてもっと多くのことができます!*
|
||||
免責事項:このデモは、表示されるすべてのファイル(例:CV_candidates.zip)を含め、完全に架空のものです。私たちは企業ではなく、候補者ではなくオープンソースの貢献者を求めています。
|
||||
|
||||
> *大阪と東京のAIスタートアップを深く調査し、少なくとも5つ見つけて、research_japan.txtファイルに保存してください*
|
||||
> 🛠️ **作業中** – 貢献者を募集中です!
|
||||
|
||||
> *C言語でテトリスゲームを作れますか?*
|
||||
## インストール
|
||||
|
||||
> *新しいプロジェクトファイルインデックスをmark2として設定したいです。*
|
||||
Chrome Driver、Docker、Python 3.10がインストールされていることを確認してください。
|
||||
|
||||
セットアップにはPython 3.10を正確に使用することを強くお勧めします。そうでない場合、依存関係のエラーが発生する可能性があります。
|
||||
|
||||
## 特徴:
|
||||
Chromeドライバーに関する問題については、**Chromedriver**セクションを参照してください。
|
||||
|
||||
- **100%ローカル**: クラウドなし、ハードウェア上で動作。データはあなたのものです。
|
||||
|
||||
- **ファイルシステムの操作**: bashを使用してファイルを簡単にナビゲートおよび操作します。
|
||||
|
||||
- **自律的なコーディング**: Python、C、Golangなどのコードを書き、デバッグし、実行できます。
|
||||
|
||||
- **エージェントルーティング**: タスクに最適なエージェントを自動的に選択します。
|
||||
|
||||
- **計画**: 複雑なタスクの場合、複数のエージェントを起動して計画および実行します。
|
||||
|
||||
- **自律的なウェブブラウジング**: 自律的なウェブナビゲーション。
|
||||
|
||||
- **メモリ**: 効率的なメモリとセッション管理。
|
||||
|
||||
---
|
||||
|
||||
## **インストール**
|
||||
|
||||
chrome driver、docker、およびpython3.10(またはそれ以降)がインストールされていることを確認してください。
|
||||
|
||||
chrome driverに関連する問題については、**Chromedriver**セクションを参照してください。
|
||||
|
||||
### 1️⃣ **リポジトリをクローンしてセットアップ**
|
||||
### 1️⃣ **リポジトリのクローンとセットアップ**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
@@ -63,7 +50,7 @@ cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2️ **仮想環境を作成**
|
||||
### 2️ **仮想環境の作成**
|
||||
|
||||
```sh
|
||||
python3 -m venv agentic_seek_env
|
||||
@@ -71,99 +58,176 @@ source agentic_seek_env/bin/activate
|
||||
# Windowsの場合: agentic_seek_env\Scripts\activate
|
||||
```
|
||||
|
||||
### 3️⃣ **パッケージをインストール**
|
||||
### 3️⃣ **パッケージのインストール**
|
||||
|
||||
**自動インストール:**
|
||||
Python、Dockerとdocker compose、Google Chromeがインストールされていることを確認してください。
|
||||
|
||||
Python 3.10.0を推奨します。
|
||||
|
||||
**自動インストール(推奨):**
|
||||
|
||||
Linux/Macosの場合:
|
||||
```sh
|
||||
./install.sh
|
||||
```
|
||||
|
||||
** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:**
|
||||
|
||||
** 注意: 日本語のテキスト読み上げ(TTS)機能には多くの依存関係が必要で、問題が発生する可能性があります。mecabrcに関する問題が発生することがあります。現在のところ、この問題を修正する方法が見つかっていません。当面は日本語でのテキスト読み上げ機能を無効にすることをお勧めします。**
|
||||
|
||||
必要なライブラリをインストールする場合は以下のコマンドを実行してください:
|
||||
|
||||
```
|
||||
pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi
|
||||
pip install unidic-lite
|
||||
python -m unidic download
|
||||
```
|
||||
|
||||
**手動で:**
|
||||
Windowsの場合:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# または
|
||||
python3 setup.py install
|
||||
./install.bat
|
||||
```
|
||||
|
||||
**手動:**
|
||||
|
||||
**注意:どのOSでも、インストールするChromeDriverがインストール済みのChromeバージョンと一致していることを確認してください。`google-chrome --version`を実行してください。Chrome >135の場合の既知の問題を参照してください。**
|
||||
|
||||
- *Linux*:
|
||||
|
||||
パッケージリストの更新:`sudo apt update`
|
||||
|
||||
依存関係のインストール:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||
|
||||
Chromeブラウザのバージョンに一致するChromeDriverのインストール:
|
||||
`sudo apt install -y chromium-chromedriver`
|
||||
|
||||
要件のインストール:`pip3 install -r requirements.txt`
|
||||
|
||||
- *Macos*:
|
||||
|
||||
brewの更新:`brew update`
|
||||
|
||||
chromedriverのインストール:`brew install --cask chromedriver`
|
||||
|
||||
portaudioのインストール:`brew install portaudio`
|
||||
|
||||
pipのアップグレード:`python3 -m pip install --upgrade pip`
|
||||
|
||||
wheelのアップグレード:`pip3 install --upgrade setuptools wheel`
|
||||
|
||||
要件のインストール:`pip3 install -r requirements.txt`
|
||||
|
||||
- *Windows*:
|
||||
|
||||
pyreadline3のインストール:`pip install pyreadline3`
|
||||
|
||||
portaudioの手動インストール(例:vcpkgまたはビルド済みバイナリ経由)後、実行:`pip install pyaudio`
|
||||
|
||||
chromedriverの手動ダウンロードとインストール:https://sites.google.com/chromium.org/driver/getting-started
|
||||
|
||||
PATHに含まれるディレクトリにchromedriverを配置します。
|
||||
|
||||
要件のインストール:`pip3 install -r requirements.txt`
|
||||
|
||||
---
|
||||
|
||||
## ローカルマシンでLLMを実行するためのセットアップ
|
||||
## マシン上でローカルにLLMを実行するためのセットアップ
|
||||
|
||||
**少なくともDeepseek 14Bを使用することをお勧めします。小さいモデルでは、特にウェブブラウジングのタスクで苦労する可能性があります。**
|
||||
**少なくともDeepseek 14Bの使用を推奨します。より小さなモデルは、特にウェブブラウジングのタスクで苦労します。**
|
||||
|
||||
**ローカルプロバイダーをセットアップする**
|
||||
|
||||
たとえば、ollamaを使用してローカルプロバイダーを開始します:
|
||||
**ローカルプロバイダーのセットアップ**
|
||||
|
||||
ローカルプロバイダーを開始します。例えばollamaの場合:
|
||||
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
以下に、サポートされているローカルプロバイダーのリストを示します。
|
||||
サポートされているローカルプロバイダーのリストについては、以下を参照してください。
|
||||
|
||||
**config.iniを更新する**
|
||||
**config.iniの更新**
|
||||
|
||||
config.iniファイルを変更して、`provider_name`をサポートされているプロバイダーに設定し、`provider_model`を`deepseek-r1:14b`に設定します。
|
||||
config.iniファイルを変更して、provider_nameをサポートされているプロバイダーに、provider_modelをプロバイダーがサポートするLLMに設定します。*Qwen*や*Deepseek*などの推論モデルを推奨します。
|
||||
|
||||
注意: `deepseek-r1:14b`は例です。ハードウェアが許可する場合は、より大きなモデルを使用してください。
|
||||
必要なハードウェアについては、READMEの最後にある**FAQ**を参照してください。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # または lm-studio、openai など
|
||||
provider_model = deepseek-r1:14b
|
||||
is_local = True # ローカルで実行するか、リモートプロバイダーで実行するか。
|
||||
provider_name = ollama # またはlm-studio、openaiなど。
|
||||
provider_model = deepseek-r1:14b # ハードウェアに合ったモデルを選択してください
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Jarvis # AIの名前
|
||||
recover_last_session = True # 前のセッションを復元するかどうか
|
||||
save_session = True # 現在のセッションを記憶するかどうか
|
||||
speak = True # テキスト読み上げ
|
||||
listen = False # 音声認識、CLIのみ
|
||||
work_dir = /Users/mlg/Documents/workspace # AgenticSeekのワークスペース。
|
||||
jarvis_personality = False # より「Jarvis」らしい性格を使用するかどうか(実験的)
|
||||
languages = en zh # 言語のリスト、テキスト読み上げはリストの最初の言語にデフォルト設定されます
|
||||
[BROWSER]
|
||||
headless_browser = True # ヘッドレスブラウザを使用するかどうか、ウェブインターフェースを使用する場合のみ推奨。
|
||||
stealth_mode = True # undetected seleniumを使用してブラウザ検出を減らす
|
||||
```
|
||||
|
||||
警告:LM-studioを使用してLLMを実行する場合、provider_nameを`openai`に設定しないでください。`lm-studio`に設定してください。
|
||||
|
||||
注意:一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例:`http://127.0.0.1:1234`
|
||||
|
||||
**ローカルプロバイダーのリスト**
|
||||
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
|
||||
| lm-studio | はい | LM studioを使用してローカルでLLMを実行(`provider_name`を`lm-studio`に設定)|
|
||||
| openai | はい | OpenAI互換APIを使用 |
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
|
||||
| lm-studio | はい | LM studioでLLMをローカル実行します(`provider_name`を`lm-studio`に設定)|
|
||||
| openai | はい | openai互換API(例:llama.cppサーバー)を使用します |
|
||||
|
||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
||||
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
|
||||
|
||||
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
|
||||
*問題が発生した場合は、**既知の問題**セクションを参照してください*
|
||||
|
||||
*ハードウェアがDeepseekをローカルで実行できない場合は、**APIを使用した実行**セクションを参照してください。*
|
||||
*ハードウェアがローカルでdeepseekを実行できない場合は、**APIで実行**セクションを参照してください*
|
||||
|
||||
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
||||
|
||||
---
|
||||
|
||||
## APIを使用したセットアップ
|
||||
## APIで実行するためのセットアップ
|
||||
|
||||
`config.ini`で希望するプロバイダーを設定してください。
|
||||
`config.ini`で目的のプロバイダーを設定します。APIプロバイダーのリストについては、以下を参照してください。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
provider_name = google
|
||||
provider_model = gemini-2.0-flash
|
||||
provider_server_address = 127.0.0.1:5000 # 関係ありません
|
||||
```
|
||||
警告:設定に末尾のスペースがないことを確認してください。
|
||||
|
||||
警告: `config.ini`に末尾のスペースがないことを確認してください。
|
||||
APIキーをエクスポートします:`export <<PROVIDER>>_API_KEY="xxx"`
|
||||
|
||||
ローカルのOpenAIベースのAPIを使用する場合は、`is_local`をTrueに設定してください。
|
||||
例:`export TOGETHER_API_KEY="xxxxx"`
|
||||
|
||||
OpenAIベースのAPIが独自のサーバーで実行されている場合は、IPアドレスを変更してください。
|
||||
**APIプロバイダーのリスト**
|
||||
|
||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| openai | 場合による | ChatGPT APIを使用 |
|
||||
| deepseek-api | いいえ | Deepseek API(非プライベート) |
|
||||
| huggingface| いいえ | Hugging-Face API(非プライベート) |
|
||||
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
|
||||
| google | いいえ | google gemini APIを使用(非プライベート) |
|
||||
|
||||
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
|
||||
*gpt-4oや他のclosedAIモデルの使用は推奨しません*。ウェブブラウジングやタスク計画のパフォーマンスが悪いです。
|
||||
|
||||
また、geminiではコーディング/bashが失敗する可能性があることに注意してください。deepseek r1用に最適化されたフォーマットのプロンプトを無視するようです。
|
||||
|
||||
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
|
||||
|
||||
*問題が発生した場合は、**既知の問題**セクションを参照してください*
|
||||
|
||||
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
||||
|
||||
@@ -171,30 +235,30 @@ OpenAIベースのAPIが独自のサーバーで実行されている場合は
|
||||
|
||||
## サービスの開始と実行
|
||||
|
||||
必要に応じてPython環境をアクティブにしてください。
|
||||
必要に応じてPython環境をアクティブ化します。
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます:
|
||||
必要なサービスを開始します。これにより、docker-compose.ymlからすべてのサービスが開始されます。これには以下が含まれます:
|
||||
- searxng
|
||||
- redis (searxngに必要)
|
||||
- フロントエンド
|
||||
- redis(searxngに必要)
|
||||
- frontend
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
start ./start_services.cmd # Window
|
||||
```
|
||||
|
||||
**オプション1:** CLIインターフェースで実行。
|
||||
**オプション1:** CLIインターフェースで実行します。
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**オプション2:** Webインターフェースで実行。
|
||||
CLIモードでは、config.iniで`headless_browser`をFalseに設定することをお勧めします。
|
||||
|
||||
注意: 現在、CLIの使用を推奨しています。Webインターフェースは開発中です。
|
||||
**オプション2:** Webインターフェースで実行します。
|
||||
|
||||
バックエンドを開始します。
|
||||
|
||||
@@ -204,97 +268,72 @@ python3 api.py
|
||||
|
||||
`http://localhost:3000/`にアクセスすると、Webインターフェースが表示されます。
|
||||
|
||||
現在、Webインターフェースではメッセージのストリーミングがサポートされていないことに注意してください。
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
`./start_services.sh`でサービスが起動していることを確認し、CLIモードの場合は`python3 cli.py`で、Webインターフェースの場合は`python3 api.py`を実行してから`localhost:3000`にアクセスしてAgenticSeekを実行します。
|
||||
|
||||
設定で`listen = True`を設定することで、音声認識を使用することもできます。CLIモードのみ。
|
||||
|
||||
終了するには、単に`goodbye`と発言/入力します。
|
||||
|
||||
以下に使用例をいくつか示します:
|
||||
|
||||
> *Pythonでスネークゲームを作って!*
|
||||
|
||||
> *フランスのレンヌでトップのカフェをウェブ検索し、3つのカフェのリストとその住所をrennes_cafes.txtに保存して。*
|
||||
|
||||
> *数値の階乗を計算するGoプログラムを書いて、それをfactorial.goとしてワークスペースに保存して。*
|
||||
|
||||
> *summer_picturesフォルダ内のすべてのJPGファイルを検索し、今日の日付で名前を変更し、名前変更されたファイルのリストをphotos_list.txtに保存して。*
|
||||
|
||||
> *2024年の人気のSF映画をオンラインで検索し、今夜観る映画を3つ選んで。リストをmovie_night.txtに保存して。*
|
||||
|
||||
> *2025年の最新AIニュース記事をウェブで検索し、3つ選択して、それらのタイトルと要約をスクレイピングするPythonスクリプトを書いて。スクリプトをnews_scraper.pyとして、要約を/home/projectsのai_news.txtに保存して。*
|
||||
|
||||
> *金曜日、無料の株価APIをウェブで検索し、supersuper7434567@gmail.comで登録し、そのAPIを使用してテスラの日々の価格を取得するPythonスクリプトを書いて、結果をstock_prices.csvに保存して。*
|
||||
|
||||
*フォーム入力機能はまだ実験的であり、失敗する可能性があることに注意してください。*
|
||||
|
||||
|
||||
|
||||
クエリを入力すると、AgenticSeekはタスクに最適なエージェントを割り当てます。
|
||||
|
||||
これは初期のプロトタイプであるため、エージェントルーティングシステムがクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
|
||||
|
||||
したがって、何をしたいのか、AIがどのように進むべきかについて非常に明確にする必要があります。たとえば、ウェブ検索を実行させたい場合は、次のように言わないでください:
|
||||
|
||||
`一人旅に適した良い国を知っていますか?`
|
||||
|
||||
代わりに、次のように尋ねてください:
|
||||
|
||||
`ウェブ検索をして、一人旅に最適な国を見つけてください`
|
||||
|
||||
---
|
||||
|
||||
## 使い方
|
||||
## **独自のサーバーでLLMを実行するためのセットアップ**
|
||||
|
||||
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
|
||||
|
||||
サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
`>>> `と表示されます
|
||||
これは、agenticSeekが指示を待っていることを示します。
|
||||
configで`listen = True`を設定することで、音声認識を使用することもできます。
|
||||
|
||||
終了するには、単に`goodbye`と言います。
|
||||
|
||||
以下は使用例です:
|
||||
|
||||
### コーディング/バッシュ
|
||||
|
||||
> *Pythonでスネークゲームを作成*
|
||||
|
||||
> *C言語で行列の掛け算を教えて*
|
||||
|
||||
> *Golangでブラックジャックを作成*
|
||||
|
||||
### ウェブ検索
|
||||
|
||||
> *日本の最先端のAI研究を行っているクールなテックスタートアップを見つけるためにウェブ検索を行う*
|
||||
|
||||
> *agenticSeekを作成したのは誰かをインターネットで見つけることができますか?*
|
||||
|
||||
> *オンラインの燃料計算機を使用して、ニースからミラノまでの旅行の費用を見積もることができますか?*
|
||||
|
||||
### ファイルシステム
|
||||
|
||||
> *契約書.pdfがどこにあるか見つけてくれませんか?*
|
||||
|
||||
> *ディスクにどれだけの空き容量があるか教えて*
|
||||
|
||||
> *READMEを読んでプロジェクトを/home/path/projectにインストールしてください*
|
||||
|
||||
### カジュアル
|
||||
|
||||
> *フランスのレンヌについて教えて*
|
||||
|
||||
> *博士号を追求すべきですか?*
|
||||
|
||||
> *最高のワークアウトルーチンは何ですか?*
|
||||
|
||||
|
||||
クエリを入力すると、agenticSeekはタスクに最適なエージェントを割り当てます。
|
||||
|
||||
これは初期のプロトタイプであるため、エージェントルーティングシステムはクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
|
||||
|
||||
したがって、何を望んでいるか、AIがどのように進行するかについて非常に明確にする必要があります。たとえば、ウェブ検索を行いたい場合は、次のように言わないでください:
|
||||
|
||||
`一人旅に良い国を知っていますか?`
|
||||
|
||||
代わりに、次のように尋ねてください:
|
||||
|
||||
`ウェブ検索を行い、一人旅に最適な国を見つけてください`
|
||||
|
||||
---
|
||||
|
||||
## **ボーナス: 自分のサーバーでLLMを実行するためのセットアップ**
|
||||
|
||||
強力なコンピュータやサーバーを持っていて、それをラップトップから使用したい場合、リモートサーバーでLLMを実行するオプションがあります。
|
||||
強力なコンピューターまたは使用できるサーバーがあるが、ラップトップから使用したい場合は、カスタムLLMサーバーを使用してリモートサーバーでLLMを実行するオプションがあります。
|
||||
|
||||
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP
|
||||
curl https://ipinfo.io/ip # 公開IP
|
||||
curl https://ipinfo.io/ip # パブリックIP
|
||||
```
|
||||
|
||||
注意: WindowsまたはmacOSの場合、IPアドレスを見つけるには、それぞれ`ipconfig`または`ifconfig`を使用してください。
|
||||
注意:WindowsまたはmacOSの場合、それぞれipconfigまたはifconfigを使用してIPアドレスを見つけます。
|
||||
|
||||
リポジトリをクローンし、`server/`フォルダに入ります。
|
||||
|
||||
リポジトリをクローンし、`server/`フォルダーに移動します。
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
サーバー固有の依存関係をインストールします:
|
||||
サーバー固有の要件をインストールします:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
@@ -306,11 +345,12 @@ pip3 install -r requirements.txt
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
`ollama`と`llamacpp`のどちらかをLLMサービスとして選択できます。
|
||||
LLMサービスとして`ollama`と`llamacpp`のどちらかを選択できます。
|
||||
|
||||
次に、個人用コンピュータで以下を行います:
|
||||
|
||||
`config.ini`ファイルを変更し、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。
|
||||
次に、個人のコンピュータで:
|
||||
|
||||
`config.ini`ファイルを変更して、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。
|
||||
`provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。
|
||||
|
||||
```sh
|
||||
@@ -321,46 +361,46 @@ provider_model = deepseek-r1:70b
|
||||
provider_server_address = x.x.x.x:3333
|
||||
```
|
||||
|
||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
||||
|
||||
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
|
||||
|
||||
---
|
||||
|
||||
## 音声認識
|
||||
|
||||
現在、音声認識は英語でのみ動作することに注意してください。
|
||||
現在、音声認識は英語でのみ機能することに注意してください。
|
||||
|
||||
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
||||
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
||||
|
||||
```
|
||||
listen = True
|
||||
```
|
||||
|
||||
有効にすると、音声認識機能はトリガーキーワード(エージェントの名前)を待ちます。その後、入力を処理します。エージェントの名前は*config.ini*ファイルの`agent_name`値を更新することでカスタマイズできます:
|
||||
有効にすると、音声認識機能は、入力を処理し始める前にトリガーキーワード(エージェントの名前)をリッスンします。*config.ini*ファイルで`agent_name`の値を更新することで、エージェントの名前をカスタマイズできます:
|
||||
|
||||
```
|
||||
agent_name = Friday
|
||||
```
|
||||
|
||||
最適な認識のために、"John"や"Emma"のような一般的な英語の名前をエージェント名として使用することをお勧めします。
|
||||
最適な認識のためには、エージェント名として「John」や「Emma」のような一般的な英語の名前を使用することをお勧めします。
|
||||
|
||||
トランスクリプトが表示され始めたら、エージェントの名前を大声で言って起動します(例:"Friday")。
|
||||
トランスクリプトが表示され始めたら、エージェントの名前を声に出して起動します(例:「Friday」)。
|
||||
|
||||
クエリを明確に話します。
|
||||
クエリをはっきりと話します。
|
||||
|
||||
リクエストを終了する際に確認フレーズを使用してシステムに進行を通知します。確認フレーズの例には次のようなものがあります:
|
||||
システムに処理を進めるよう合図するために、確認フレーズでリクエストを終了します。確認フレーズの例は次のとおりです:
|
||||
```
|
||||
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||
```
|
||||
|
||||
## 設定
|
||||
|
||||
設定例:
|
||||
設定例:
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:1.5b
|
||||
provider_model = deepseek-r1:32b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
@@ -369,7 +409,7 @@ speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
jarvis_personality = False
|
||||
languages = en ja
|
||||
languages = en zh
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
@@ -377,51 +417,66 @@ stealth_mode = False
|
||||
|
||||
**説明**:
|
||||
|
||||
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。
|
||||
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)。
|
||||
- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。
|
||||
- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。
|
||||
- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます。
|
||||
- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。
|
||||
- save_session -> セッションデータを保存する(True)か、しない(False)。
|
||||
- speak -> 音声出力を有効にする(True)か、しない(False)。
|
||||
- listen -> 音声入力を有効にする(True)か、しない(False)。
|
||||
- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。
|
||||
- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)か、しない(False)。これは単にプロンプトファイルを変更するだけです。
|
||||
- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。
|
||||
- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
|
||||
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
|
||||
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)か。
|
||||
|
||||
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)
|
||||
|
||||
- provider_model -> 使用するモデル、例:deepseek-r1:32b。
|
||||
|
||||
- provider_server_address -> サーバーアドレス、例:ローカルの場合は127.0.0.1:11434。非ローカルAPIの場合は何でも設定します。
|
||||
|
||||
- agent_name -> エージェントの名前、例:Friday。TTSのトリガーワードとして使用されます。
|
||||
|
||||
- recover_last_session -> 前回のセッションから再開する(True)かしない(False)か。
|
||||
|
||||
- save_session -> セッションデータを保存する(True)かしない(False)か。
|
||||
|
||||
- speak -> 音声出力を有効にする(True)かしない(False)か。
|
||||
|
||||
- listen -> 音声入力をリッスンする(True)かしない(False)か。
|
||||
|
||||
- work_dir -> AIがアクセスできるフォルダ。例:/Users/user/Documents/。
|
||||
|
||||
- jarvis_personality -> JARVISのような性格を使用する(True)かしない(False)か。これは単にプロンプトファイルを変更します。
|
||||
|
||||
- languages -> サポートされている言語のリスト。LLMルーターが正しく機能するために必要です。あまりにも多くの言語や類似した言語を入れすぎないようにしてください。
|
||||
|
||||
- headless_browser -> 表示ウィンドウなしでブラウザを実行する(True)かしない(False)か。
|
||||
|
||||
- stealth_mode -> ボット検出を困難にします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
|
||||
|
||||
- languages -> サポートされている言語のリスト。エージェントルーティングシステムに必要です。言語リストが長いほど、ダウンロードされるモデルが多くなります。
|
||||
|
||||
## プロバイダー
|
||||
|
||||
以下の表は利用可能なプロバイダーを示しています:
|
||||
以下の表は、利用可能なプロバイダーを示しています:
|
||||
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
|
||||
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 |
|
||||
| lm-studio | はい | LM studio(`lm-studio`)を使用してローカルでLLMを実行 |
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
|
||||
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行します |
|
||||
| lm-studio | はい | LM studioでLLMをローカル実行します(`lm-studio`) |
|
||||
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
|
||||
| deepseek-api | いいえ | Deepseek API(非プライベート) |
|
||||
| huggingface| いいえ | Hugging-Face API(非プライベート) |
|
||||
| togetherAI | いいえ | together AI API(非プライベート)を使用
|
||||
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
|
||||
| google | いいえ | google gemini APIを使用(非プライベート) |
|
||||
|
||||
|
||||
プロバイダーを選択するには、config.iniを変更します:
|
||||
プロバイダーを選択するには、config.iniを変更します:
|
||||
|
||||
```
|
||||
is_local = False
|
||||
provider_name = openai
|
||||
provider_model = gpt-4o
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:32b
|
||||
provider_server_address = 127.0.0.1:5000
|
||||
```
|
||||
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalse。
|
||||
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalseである必要があります。
|
||||
|
||||
`provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。
|
||||
|
||||
`provider_model`: エージェントが使用するモデルを設定します。
|
||||
|
||||
`provider_server_address`: サーバープロバイダーを使用しない場合は何でも設定できます。
|
||||
`provider_server_address`: サーバーアドレス。APIプロバイダーには使用されません。
|
||||
|
||||
# 既知の問題
|
||||
|
||||
@@ -434,55 +489,82 @@ Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
|
||||
|
||||
最新バージョンをダウンロードするには、次のリンクにアクセスしてください:
|
||||
最新バージョンをダウンロードするためにナビゲートする必要があります:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください:
|
||||
Chromeバージョン115以降を使用している場合は、以下にアクセスしてください:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
お使いのOSに対応するchromedriverバージョンをダウンロードします。
|
||||
そして、OSに一致するchromedriverバージョンをダウンロードします。
|
||||
|
||||

|
||||

|
||||
|
||||
このセクションが不完全な場合は、問題を報告してください。
|
||||
このセクションが不完全な場合は、問題を提起してください。
|
||||
|
||||
## 接続アダプタの問題
|
||||
|
||||
```
|
||||
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions'
|
||||
```
|
||||
|
||||
プロバイダーのIPアドレスの前に`http://`があることを確認してください:
|
||||
|
||||
`provider_server_address = http://127.0.0.1:11434`
|
||||
|
||||
## SearxNGのベースURLを指定する必要があります
|
||||
|
||||
```
|
||||
raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.")
|
||||
ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.
|
||||
```
|
||||
|
||||
`.env.example`を`.env`として移動しなかった可能性がありますか?SEARXNG_BASE_URLをエクスポートすることもできます:
|
||||
|
||||
`export SEARXNG_BASE_URL="http://127.0.0.1:8080"`
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: どのようなハードウェアが必要ですか?**
|
||||
|
||||
| モデルサイズ | GPU | コメント |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| 7B | 8GB VRAM | ⚠️ 推奨されません。パフォーマンスが低く、頻繁に幻覚を起こし、プランナーエージェントが失敗する可能性が高いです。 |
|
||||
| 14B | 12GB VRAM (例: RTX 3060) | ✅ 簡単なタスクには使用可能です。ウェブブラウジングや計画タスクには苦労する可能性があります。 |
|
||||
| 32B | 24GB以上のVRAM (例: RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画にはまだ苦労する可能性があります。 |
|
||||
| 70B+ | 48GB以上のVRAM (例: Mac Studio) | 💪 優れた性能。高度なユースケースに推奨されます。 |
|
||||
|-----------|------------|--------------------------------------------------------------------------|
|
||||
| 7B | 8GB VRAM | ⚠️ 非推奨。パフォーマンスが悪く、幻覚が頻繁に発生し、プランナーエージェントは失敗する可能性が高いです。 |
|
||||
| 14B | 12GB VRAM(例:RTX 3060) | ✅ 簡単なタスクには使用可能。ウェブブラウジングや計画タスクで苦労する可能性があります。 |
|
||||
| 32B | 24GB以上のVRAM(例:RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画でまだ苦労する可能性があります。 |
|
||||
| 70B+ | 48GB以上のVRAM(例:mac studio) | 💪 素晴らしい。高度なユースケースに推奨されます。 |
|
||||
|
||||
**Q: なぜ他のモデルではなくDeepseek R1を選ぶのですか?**
|
||||
**Q: なぜ他のモデルではなくDeepseek R1なのですか?**
|
||||
|
||||
Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
|
||||
Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに合っていると考えており、他のモデルも正常に動作しますが、Deepseekが私たちの主要な選択肢です。
|
||||
|
||||
**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?**
|
||||
|
||||
Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
|
||||
ローカルが実行されていること(`ollama serve`)、`config.ini`がプロバイダーと一致していること、依存関係がインストールされていることを確認してください。それでも解決しない場合は、遠慮なく問題を提起してください。
|
||||
|
||||
**Q: 本当に100%ローカルで実行できますか?**
|
||||
|
||||
はい、OllamaまたはServerプロバイダーを使用すると、すべての音声認識、LLM、および音声合成モデルがローカルで実行されます。非ローカルオプション(OpenAIまたは他のAPI)はオプションです。
|
||||
はい、Ollama、lm-studio、またはサーバープロバイダーを使用すると、すべての音声認識、LLM、テキスト読み上げモデルがローカルで実行されます。非ローカルオプション(OpenAIまたはその他のAPI)はオプションです。
|
||||
|
||||
**Q: Manusを持っているのに、なぜAgenticSeekを使用する必要があるのですか?**
|
||||
**Q: Manusがあるのに、なぜAgenticSeekを使うべきなのですか?**
|
||||
|
||||
これは、AIエージェントに関する興味から始まったサイドプロジェクトです。特別な点は、ローカルモデルを使用し、APIを避けることです。
|
||||
私たちは、JarvisやFriday(アイアンマン映画)からインスピレーションを得て、「クール」にしようとしましたが、機能性に関してはManusから多くのインスピレーションを得ています。なぜなら、人々が最初に求めているのはローカルのManusの代替品だからです。
|
||||
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIのコストを回避します。
|
||||
これは、AIエージェントへの関心から始めたサイドプロジェクトです。特別なのは、ローカルモデルを使用し、APIを避けたいということです。
|
||||
私たちはJarvisとFriday(アイアンマン映画)からインスピレーションを得て「クール」にしましたが、機能性についてはManusからより多くのインスピレーションを得ています。なぜなら、それが人々が最初に望むもの、つまりローカルなManusの代替だからです。
|
||||
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIコストを回避します。
|
||||
|
||||
## 貢献
|
||||
## 貢献する
|
||||
|
||||
AgenticSeekを改善するための開発者を探しています!オープンな問題やディスカッションを確認してください。
|
||||
AgenticSeekを改善するための開発者を募集しています!オープンな問題やディスカッションを確認してください。
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
[貢献ガイド](./docs/CONTRIBUTING.md)
|
||||
|
||||
## 著者:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
## メンテナー:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | パリ時間
|
||||
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北時間 |(常に忙しい)
|
||||
@@ -34,7 +34,7 @@ config.read('config.ini')
|
||||
|
||||
api.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_origins=["http://localhost", "http://localhost:3000"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@@ -58,7 +58,7 @@ def initialize_system():
|
||||
logger.info(f"Provider initialized: {provider.provider_name} ({provider.model})")
|
||||
|
||||
browser = Browser(
|
||||
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
|
||||
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode, lang=languages[0]),
|
||||
anticaptcha_manual_install=stealth_mode
|
||||
)
|
||||
logger.info("Browser initialized")
|
||||
@@ -128,6 +128,12 @@ async def is_active():
|
||||
logger.info("Is active endpoint called")
|
||||
return {"is_active": interaction.is_active}
|
||||
|
||||
@api.get("/stop")
|
||||
async def stop():
|
||||
logger.info("Stop endpoint called")
|
||||
interaction.current_agent.request_stop()
|
||||
return JSONResponse(status_code=200, content={"status": "stopped"})
|
||||
|
||||
@api.get("/latest_answer")
|
||||
async def get_latest_answer():
|
||||
global query_resp_history
|
||||
@@ -138,6 +144,7 @@ async def get_latest_answer():
|
||||
query_resp = {
|
||||
"done": "false",
|
||||
"answer": interaction.current_agent.last_answer,
|
||||
"reasoning": interaction.current_agent.last_reasoning,
|
||||
"agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None",
|
||||
"success": interaction.current_agent.success,
|
||||
"blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {},
|
||||
@@ -145,6 +152,7 @@ async def get_latest_answer():
|
||||
"uid": uid
|
||||
}
|
||||
interaction.current_agent.last_answer = ""
|
||||
interaction.current_agent.last_reasoning = ""
|
||||
query_resp_history.append(query_resp)
|
||||
return JSONResponse(status_code=200, content=query_resp)
|
||||
if query_resp_history:
|
||||
@@ -158,6 +166,7 @@ async def think_wrapper(interaction, query):
|
||||
success = await interaction.think()
|
||||
if not success:
|
||||
interaction.last_answer = "Error: No answer from agent"
|
||||
interaction.last_reasoning = "Error: No reasoning from agent"
|
||||
interaction.last_success = False
|
||||
else:
|
||||
interaction.last_success = True
|
||||
@@ -166,7 +175,8 @@ async def think_wrapper(interaction, query):
|
||||
return success
|
||||
except Exception as e:
|
||||
logger.error(f"Error in think_wrapper: {str(e)}")
|
||||
interaction.last_answer = f"Error: {str(e)}"
|
||||
interaction.last_answer = f""
|
||||
interaction.last_reasoning = f"Error: {str(e)}"
|
||||
interaction.last_success = False
|
||||
raise e
|
||||
|
||||
@@ -177,6 +187,7 @@ async def process_query(request: QueryRequest):
|
||||
query_resp = QueryResponse(
|
||||
done="false",
|
||||
answer="",
|
||||
reasoning="",
|
||||
agent_name="Unknown",
|
||||
success="false",
|
||||
blocks={},
|
||||
@@ -194,6 +205,7 @@ async def process_query(request: QueryRequest):
|
||||
|
||||
if not success:
|
||||
query_resp.answer = interaction.last_answer
|
||||
query_resp.reasoning = interaction.last_reasoning
|
||||
return JSONResponse(status_code=400, content=query_resp.jsonify())
|
||||
|
||||
if interaction.current_agent:
|
||||
@@ -208,11 +220,11 @@ async def process_query(request: QueryRequest):
|
||||
logger.info(f"Blocks: {blocks_json}")
|
||||
query_resp.done = "true"
|
||||
query_resp.answer = interaction.last_answer
|
||||
query_resp.reasoning = interaction.last_reasoning
|
||||
query_resp.agent_name = interaction.current_agent.agent_name
|
||||
query_resp.success = str(interaction.last_success)
|
||||
query_resp.blocks = blocks_json
|
||||
|
||||
# Store the raw dictionary representation
|
||||
query_resp_dict = {
|
||||
"done": query_resp.done,
|
||||
"answer": query_resp.answer,
|
||||
|
||||
@@ -29,7 +29,7 @@ async def main():
|
||||
is_local=config.getboolean('MAIN', 'is_local'))
|
||||
|
||||
browser = Browser(
|
||||
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
|
||||
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode, lang=languages[0]),
|
||||
anticaptcha_manual_install=stealth_mode
|
||||
)
|
||||
|
||||
@@ -49,9 +49,9 @@ async def main():
|
||||
PlannerAgent(name="Planner",
|
||||
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
|
||||
provider=provider, verbose=False, browser=browser),
|
||||
McpAgent(name="MCP Agent",
|
||||
prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
|
||||
provider=provider, verbose=False),
|
||||
#McpAgent(name="MCP Agent",
|
||||
# prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
|
||||
# provider=provider, verbose=False), # NOTE under development
|
||||
]
|
||||
|
||||
interaction = Interaction(agents,
|
||||
|
||||
+1177
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
"axios": "^1.8.4",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
|
||||
@@ -167,6 +167,88 @@ body {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
/* Message header layout */
|
||||
.message-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.reasoning-toggle {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.reasoning-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.reasoning-toggle:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
/* Reasoning content container */
|
||||
.reasoning-content {
|
||||
margin-top: 12px;
|
||||
padding: 12px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-left: 3px solid rgba(255, 255, 255, 0.3);
|
||||
border-radius: 0 4px 4px 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.reasoning-content h1,
|
||||
.reasoning-content h2,
|
||||
.reasoning-content h3,
|
||||
.reasoning-content h4,
|
||||
.reasoning-content h5,
|
||||
.reasoning-content h6 {
|
||||
font-size: 1em;
|
||||
margin: 8px 0 4px 0;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
.reasoning-content p {
|
||||
margin: 6px 0;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
/* Alternative light theme styles */
|
||||
.message.user-message .reasoning-toggle {
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
border-color: rgba(0, 0, 0, 0.1);
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.message.user-message .reasoning-toggle:hover {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.message.user-message .reasoning-content {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
border-left-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.message.user-message .reasoning-content p {
|
||||
color: rgba(0, 0, 0, 0.7);
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
text-align: center;
|
||||
color: #64748b; /* lighter gray */
|
||||
@@ -366,6 +448,10 @@ body {
|
||||
color: #28a745; /* success */
|
||||
}
|
||||
|
||||
.block-failure {
|
||||
color: #d21b0b; /* success */
|
||||
}
|
||||
|
||||
.block pre {
|
||||
background-color: #1a202c; /* Darker than darkCard */
|
||||
padding: 12px;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import axios from 'axios';
|
||||
import './App.css';
|
||||
import { colors } from './colors';
|
||||
@@ -12,6 +13,7 @@ function App() {
|
||||
const [responseData, setResponseData] = useState(null);
|
||||
const [isOnline, setIsOnline] = useState(false);
|
||||
const [status, setStatus] = useState('Agents ready');
|
||||
const [expandedReasoning, setExpandedReasoning] = useState(new Set());
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -25,7 +27,7 @@ function App() {
|
||||
|
||||
const checkHealth = async () => {
|
||||
try {
|
||||
await axios.get('http://0.0.0.0:8000/health');
|
||||
await axios.get('http://127.0.0.1:8000/health');
|
||||
setIsOnline(true);
|
||||
console.log('System is online');
|
||||
} catch {
|
||||
@@ -37,7 +39,7 @@ function App() {
|
||||
const fetchScreenshot = async () => {
|
||||
try {
|
||||
const timestamp = new Date().getTime();
|
||||
const res = await axios.get(`http://0.0.0.0:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, {
|
||||
const res = await axios.get(`http://127.0.0.1:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
console.log('Screenshot fetched successfully');
|
||||
@@ -74,9 +76,21 @@ function App() {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
const toggleReasoning = (messageIndex) => {
|
||||
setExpandedReasoning(prev => {
|
||||
const newSet = new Set(prev);
|
||||
if (newSet.has(messageIndex)) {
|
||||
newSet.delete(messageIndex);
|
||||
} else {
|
||||
newSet.add(messageIndex);
|
||||
}
|
||||
return newSet;
|
||||
});
|
||||
};
|
||||
|
||||
const fetchLatestAnswer = async () => {
|
||||
try {
|
||||
const res = await axios.get('http://0.0.0.0:8000/latest_answer');
|
||||
const res = await axios.get('http://127.0.0.1:8000/latest_answer');
|
||||
const data = res.data;
|
||||
|
||||
updateData(data);
|
||||
@@ -93,6 +107,7 @@ function App() {
|
||||
{
|
||||
type: 'agent',
|
||||
content: data.answer,
|
||||
reasoning: data.reasoning,
|
||||
agentName: data.agent_name,
|
||||
status: data.status,
|
||||
uid: data.uid,
|
||||
@@ -120,6 +135,19 @@ function App() {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleStop = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
setIsLoading(false);
|
||||
setError(null);
|
||||
try {
|
||||
const res = await axios.get('http://127.0.0.1:8000/stop');
|
||||
setStatus("Requesting stop...");
|
||||
} catch (err) {
|
||||
console.error('Error stopping the agent:', err);
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubmit = async (e) => {
|
||||
e.preventDefault();
|
||||
checkHealth();
|
||||
@@ -134,7 +162,7 @@ function App() {
|
||||
try {
|
||||
console.log('Sending query:', query);
|
||||
setQuery('waiting for response...');
|
||||
const res = await axios.post('http://0.0.0.0:8000/query', {
|
||||
const res = await axios.post('http://127.0.0.1:8000/query', {
|
||||
query,
|
||||
tts_enabled: false
|
||||
});
|
||||
@@ -171,8 +199,6 @@ function App() {
|
||||
</header>
|
||||
<main className="main">
|
||||
<div className="app-sections">
|
||||
|
||||
|
||||
<div className="chat-section">
|
||||
<h2>Chat Interface</h2>
|
||||
<div className="messages">
|
||||
@@ -190,10 +216,28 @@ function App() {
|
||||
: 'error-message'
|
||||
}`}
|
||||
>
|
||||
<div className="message-header">
|
||||
{msg.type === 'agent' && (
|
||||
<span className="agent-name">{msg.agentName}</span>
|
||||
)}
|
||||
<p>{msg.content}</p>
|
||||
{msg.type === 'agent' && msg.reasoning && expandedReasoning.has(index) && (
|
||||
<div className="reasoning-content">
|
||||
<ReactMarkdown>{msg.reasoning}</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
{msg.type === 'agent' && (
|
||||
<button
|
||||
className="reasoning-toggle"
|
||||
onClick={() => toggleReasoning(index)}
|
||||
title={expandedReasoning.has(index) ? "Hide reasoning" : "Show reasoning"}
|
||||
>
|
||||
{expandedReasoning.has(index) ? '▼' : '▶'} Reasoning
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="message-content">
|
||||
<ReactMarkdown>{msg.content}</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -212,6 +256,9 @@ function App() {
|
||||
<button type="submit" disabled={isLoading}>
|
||||
Send
|
||||
</button>
|
||||
<button onClick={handleStop}>
|
||||
Stop
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -241,9 +288,11 @@ function App() {
|
||||
<p className="block-tool">Tool: {block.tool_type}</p>
|
||||
<pre>{block.block}</pre>
|
||||
<p className="block-feedback">Feedback: {block.feedback}</p>
|
||||
<p className="block-success">
|
||||
Success: {block.success ? 'Yes' : 'No'}
|
||||
</p>
|
||||
{block.success ? (
|
||||
<p className="block-success">Success</p>
|
||||
) : (
|
||||
<p className="block-failure">Failure</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -3,10 +3,22 @@
|
||||
echo "Starting installation for Linux..."
|
||||
|
||||
set -e
|
||||
|
||||
if ! command -v python3.10 &> /dev/null; then
|
||||
echo "Error: Python 3.10 is not installed. Please install Python 3.10 and try again."
|
||||
echo "You can install it using: sudo apt-get install python3.10 python3.10-dev python3.10-venv"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if pip3.10 is available
|
||||
if ! python3.10 -m pip --version &> /dev/null; then
|
||||
echo "Error: pip for Python 3.10 is not installed. Installing python3.10-pip..."
|
||||
sudo apt-get install -y python3.10-pip || { echo "Failed to install python3.10-pip"; exit 1; }
|
||||
fi
|
||||
|
||||
# Update package list
|
||||
sudo apt-get update || { echo "Failed to update package list"; exit 1; }
|
||||
# make sure essential tool are installed
|
||||
# Install essential tools
|
||||
sudo apt-get install -y \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
@@ -21,15 +33,15 @@ sudo apt-get install -y \
|
||||
libnss3 \
|
||||
libxss1 || { echo "Failed to install packages"; exit 1; }
|
||||
|
||||
# upgrade pip
|
||||
pip install --upgrade pip
|
||||
# install wheel
|
||||
pip install --upgrade pip setuptools wheel
|
||||
# Upgrade pip for Python 3.10
|
||||
python3.10 -m pip install --upgrade pip || { echo "Failed to upgrade pip"; exit 1; }
|
||||
# Install and upgrade setuptools and wheel
|
||||
python3.10 -m pip install --upgrade setuptools wheel || { echo "Failed to install setuptools and wheel"; exit 1; }
|
||||
# Install Selenium for chromedriver
|
||||
python3.10 -m pip install selenium || { echo "Failed to install selenium"; exit 1; }
|
||||
# Install Python dependencies from requirements.txt
|
||||
python3.10 -m pip install -r requirements.txt --no-cache-dir || { echo "Failed to install requirements.txt"; exit 1; }
|
||||
# install docker compose
|
||||
sudo apt install -y docker-compose
|
||||
# Install Selenium for chromedriver
|
||||
pip3 install selenium
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt --no-cache-dir
|
||||
|
||||
echo "Installation complete for Linux!"
|
||||
@@ -4,6 +4,18 @@ echo "Starting installation for macOS..."
|
||||
|
||||
set -e
|
||||
|
||||
if ! command -v python3.10 &> /dev/null; then
|
||||
echo "Error: Python 3.10 is not installed. Please install Python 3.10 and try again."
|
||||
echo "You can install it using: sudo apt-get install python3.10 python3.10-dev python3.10-venv"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if pip3.10 is available
|
||||
if ! python3.10 -m pip --version &> /dev/null; then
|
||||
echo "Error: pip for Python 3.10 is not installed. Installing python3.10-pip..."
|
||||
sudo apt-get install -y python3.10-pip || { echo "Failed to install python3.10-pip"; exit 1; }
|
||||
fi
|
||||
|
||||
# Check if homebrew is installed
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Homebrew not found. Installing Homebrew..."
|
||||
@@ -18,13 +30,14 @@ brew install wget
|
||||
brew install --cask chromedriver
|
||||
# Install portaudio for pyAudio using Homebrew
|
||||
brew install portaudio
|
||||
# update pip
|
||||
python3 -m pip install --upgrade pip
|
||||
# upgrade setuptools and wheel
|
||||
pip3 install --upgrade setuptools wheel
|
||||
# Install Selenium
|
||||
pip3 install selenium
|
||||
|
||||
# Upgrade pip for Python 3.10
|
||||
python3.10 -m pip install --upgrade pip || { echo "Failed to upgrade pip"; exit 1; }
|
||||
# Install and upgrade setuptools and wheel
|
||||
python3.10 -m pip install --upgrade setuptools wheel || { echo "Failed to install setuptools and wheel"; exit 1; }
|
||||
# Install Selenium for chromedriver
|
||||
python3.10 -m pip install selenium || { echo "Failed to install selenium"; exit 1; }
|
||||
# Install Python dependencies from requirements.txt
|
||||
pip3 install -r requirements.txt --no-cache-dir
|
||||
python3.10 -m pip install -r requirements.txt --no-cache-dir || { echo "Failed to install requirements.txt"; exit 1; }
|
||||
|
||||
echo "Installation complete for macOS!"
|
||||
File diff suppressed because it is too large
Load Diff
+17
-2
@@ -44,7 +44,9 @@ class Agent():
|
||||
self.blocks_result = []
|
||||
self.success = True
|
||||
self.last_answer = ""
|
||||
self.last_reasoning = ""
|
||||
self.status_message = "Haven't started yet"
|
||||
self.stop = False
|
||||
self.verbose = verbose
|
||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||
|
||||
@@ -64,6 +66,10 @@ class Agent():
|
||||
def get_last_answer(self) -> str:
|
||||
return self.last_answer
|
||||
|
||||
@property
|
||||
def get_last_reasoning(self) -> str:
|
||||
return self.last_reasoning
|
||||
|
||||
@property
|
||||
def get_blocks(self) -> list:
|
||||
return self.blocks_result
|
||||
@@ -114,6 +120,13 @@ class Agent():
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def request_stop(self) -> None:
|
||||
"""
|
||||
Request the agent to stop.
|
||||
"""
|
||||
self.stop = True
|
||||
self.status_message = "Stopped"
|
||||
|
||||
@abstractmethod
|
||||
def process(self, prompt, speech_module) -> str:
|
||||
"""
|
||||
@@ -127,8 +140,10 @@ class Agent():
|
||||
Remove the reasoning block of reasoning model like deepseek.
|
||||
"""
|
||||
end_tag = "</think>"
|
||||
end_idx = text.rfind(end_tag)+8
|
||||
return text[end_idx:]
|
||||
end_idx = text.rfind(end_tag)
|
||||
if end_idx == -1:
|
||||
return text
|
||||
return text[end_idx+8:]
|
||||
|
||||
def extract_reasoning_text(self, text: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -181,6 +181,7 @@ class BrowserAgent(Agent):
|
||||
animate_thinking("Thinking...", color="status")
|
||||
self.memory.push('user', prompt)
|
||||
answer, reasoning = await self.llm_request()
|
||||
self.last_reasoning = reasoning
|
||||
if show_reasoning:
|
||||
pretty_print(reasoning, color="failure")
|
||||
pretty_print(answer, color="output")
|
||||
@@ -243,10 +244,10 @@ class BrowserAgent(Agent):
|
||||
self.logger.warning("No link selected.")
|
||||
return None
|
||||
|
||||
def get_page_text(self, compression = False) -> str:
|
||||
def get_page_text(self, limit_to_model_ctx = False) -> str:
|
||||
"""Get the text content of the current page."""
|
||||
page_text = self.browser.get_text()
|
||||
if compression:
|
||||
if limit_to_model_ctx:
|
||||
#page_text = self.memory.compress_text_to_max_ctx(page_text)
|
||||
page_text = self.memory.trim_text_to_max_ctx(page_text)
|
||||
return page_text
|
||||
@@ -349,11 +350,13 @@ class BrowserAgent(Agent):
|
||||
self.show_search_results(search_result)
|
||||
prompt = self.make_newsearch_prompt(user_prompt, search_result)
|
||||
unvisited = [None]
|
||||
while not complete and len(unvisited) > 0:
|
||||
|
||||
while not complete and len(unvisited) > 0 and not self.stop:
|
||||
self.memory.clear()
|
||||
unvisited = self.select_unvisited(search_result)
|
||||
answer, reasoning = await self.llm_decide(prompt, show_reasoning = False)
|
||||
if self.stop:
|
||||
pretty_print(f"Requested stop.", color="failure")
|
||||
break
|
||||
if self.last_answer == answer:
|
||||
prompt = self.stuck_prompt(user_prompt, unvisited)
|
||||
continue
|
||||
@@ -365,13 +368,13 @@ class BrowserAgent(Agent):
|
||||
self.status_message = "Filling web form..."
|
||||
pretty_print(f"Filling inputs form...", color="status")
|
||||
fill_success = self.browser.fill_form(extracted_form)
|
||||
page_text = self.get_page_text()
|
||||
page_text = self.get_page_text(limit_to_model_ctx=True)
|
||||
answer = self.handle_update_prompt(user_prompt, page_text, fill_success)
|
||||
answer, reasoning = await self.llm_decide(prompt)
|
||||
|
||||
if Action.FORM_FILLED.value in answer:
|
||||
pretty_print(f"Filled form. Handling page update.", color="status")
|
||||
page_text = self.get_page_text()
|
||||
page_text = self.get_page_text(limit_to_model_ctx=True)
|
||||
self.navigable_links = self.browser.get_navigable()
|
||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||
continue
|
||||
@@ -407,7 +410,7 @@ class BrowserAgent(Agent):
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
continue
|
||||
self.current_page = link
|
||||
page_text = self.get_page_text()
|
||||
page_text = self.get_page_text(limit_to_model_ctx=True)
|
||||
self.navigable_links = self.browser.get_navigable()
|
||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||
self.status_message = "Navigating..."
|
||||
|
||||
@@ -51,10 +51,12 @@ class CoderAgent(Agent):
|
||||
self.memory.push('user', prompt)
|
||||
clarify_trigger = "REQUEST_CLARIFICATION"
|
||||
|
||||
while attempt < max_attempts:
|
||||
while attempt < max_attempts and not self.stop:
|
||||
print("Stopped?", self.stop)
|
||||
animate_thinking("Thinking...", color="status")
|
||||
await self.wait_message(speech_module)
|
||||
answer, reasoning = await self.llm_request()
|
||||
self.last_reasoning = reasoning
|
||||
if clarify_trigger in answer:
|
||||
self.last_answer = answer
|
||||
await asyncio.sleep(0)
|
||||
|
||||
@@ -28,10 +28,11 @@ class FileAgent(Agent):
|
||||
exec_success = False
|
||||
prompt += f"\nYou must work in directory: {self.work_dir}"
|
||||
self.memory.push('user', prompt)
|
||||
while exec_success is False:
|
||||
while exec_success is False and not self.stop:
|
||||
await self.wait_message(speech_module)
|
||||
animate_thinking("Thinking...", color="status")
|
||||
answer, reasoning = await self.llm_request()
|
||||
self.last_reasoning = reasoning
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
answer = self.remove_blocks(answer)
|
||||
self.last_answer = answer
|
||||
|
||||
@@ -35,8 +35,7 @@ class McpAgent(Agent):
|
||||
"""
|
||||
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")
|
||||
pretty_print("MCP Finder disabled.", color="warning")
|
||||
self.enabled = False
|
||||
return {
|
||||
"mcp_finder": api_key_mcp_finder
|
||||
|
||||
@@ -83,11 +83,15 @@ class PlannerAgent(Agent):
|
||||
self.logger.warning(f"Agent {task['agent']} does not exist.")
|
||||
pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
|
||||
return []
|
||||
try:
|
||||
agent = {
|
||||
'agent': task['agent'],
|
||||
'id': task['id'],
|
||||
'task': task['task']
|
||||
}
|
||||
except:
|
||||
self.logger.warning("Missing field in json plan.")
|
||||
return []
|
||||
self.logger.info(f"Created agent {task['agent']} with task: {task['task']}")
|
||||
if 'need' in task:
|
||||
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
|
||||
@@ -156,6 +160,7 @@ class PlannerAgent(Agent):
|
||||
return []
|
||||
agents_tasks = self.parse_agent_tasks(answer)
|
||||
if agents_tasks == []:
|
||||
self.show_plan(agents_tasks, answer)
|
||||
prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n"
|
||||
pretty_print("Failed to make plan. Retrying...", color="warning")
|
||||
continue
|
||||
@@ -178,7 +183,11 @@ class PlannerAgent(Agent):
|
||||
last_agent_work = agents_work_result[id]
|
||||
tool_success_str = "success" if success else "failure"
|
||||
pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure")
|
||||
if int(id) == len(agents_tasks):
|
||||
try:
|
||||
id_int = int(id)
|
||||
except Exception as e:
|
||||
return agents_tasks
|
||||
if id_int == len(agents_tasks):
|
||||
next_task = "No task follow, this was the last step. If it failed add a task to recover."
|
||||
else:
|
||||
next_task = f"Next task is: {agents_tasks[int(id)][0]}."
|
||||
@@ -221,8 +230,9 @@ class PlannerAgent(Agent):
|
||||
agent_prompt = self.make_prompt(task['task'], required_infos)
|
||||
pretty_print(f"Agent {task['agent']} started working...", color="status")
|
||||
self.logger.info(f"Agent {task['agent']} started working on {task['task']}.")
|
||||
answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None)
|
||||
answer, reasoning = await self.agents[task['agent'].lower()].process(agent_prompt, None)
|
||||
self.last_answer = answer
|
||||
self.last_reasoning = reasoning
|
||||
self.blocks_result = self.agents[task['agent'].lower()].blocks_result
|
||||
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
|
||||
success = self.agents[task['agent'].lower()].get_success
|
||||
@@ -257,7 +267,7 @@ class PlannerAgent(Agent):
|
||||
return "Failed to parse the tasks.", ""
|
||||
i = 0
|
||||
steps = len(agents_tasks)
|
||||
while i < steps:
|
||||
while i < steps and not self.stop:
|
||||
task_name, task = agents_tasks[i][0], agents_tasks[i][1]
|
||||
self.status_message = "Starting agents..."
|
||||
pretty_print(f"I will {task_name}.", color="info")
|
||||
@@ -271,6 +281,8 @@ class PlannerAgent(Agent):
|
||||
answer, success = await self.start_agent_process(task, required_infos)
|
||||
except Exception as e:
|
||||
raise e
|
||||
if self.stop:
|
||||
pretty_print(f"Requested stop.", color="failure")
|
||||
agents_work_result[task['id']] = answer
|
||||
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
|
||||
steps = len(agents_tasks)
|
||||
|
||||
+86
-22
@@ -45,7 +45,7 @@ def get_chrome_path() -> str:
|
||||
paths = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium", "/opt/chrome/chrome", "/usr/local/bin/chrome"]
|
||||
|
||||
for path in paths:
|
||||
if os.path.exists(path) and os.access(path, os.X_OK): # Check if executable
|
||||
if os.path.exists(path) and os.access(path, os.X_OK):
|
||||
return path
|
||||
print("Looking for Google Chrome in these locations failed:")
|
||||
print('\n'.join(paths))
|
||||
@@ -62,9 +62,9 @@ def get_chrome_path() -> str:
|
||||
def get_random_user_agent() -> str:
|
||||
"""Get a random user agent string with associated vendor."""
|
||||
user_agents = [
|
||||
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "vendor": "Google Inc."},
|
||||
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "vendor": "Apple Inc."},
|
||||
{"ua": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", "vendor": ""},
|
||||
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Google Inc."},
|
||||
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Apple Inc."},
|
||||
{"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Google Inc."},
|
||||
]
|
||||
return random.choice(user_agents)
|
||||
|
||||
@@ -91,7 +91,7 @@ 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")
|
||||
pretty_print("Bypassing SSL verification issues, 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:
|
||||
@@ -107,11 +107,10 @@ def create_undetected_chromedriver(service, chrome_options) -> webdriver.Chrome:
|
||||
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", lang="en") -> webdriver.Chrome:
|
||||
"""Create a Chrome WebDriver with specified options."""
|
||||
chrome_options = Options()
|
||||
chrome_path = get_chrome_path()
|
||||
@@ -126,19 +125,21 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx
|
||||
chrome_options.add_argument("--disable-webgl")
|
||||
user_data_dir = tempfile.mkdtemp()
|
||||
user_agent = get_random_user_agent()
|
||||
width, height = (1920, 1080)
|
||||
chrome_options.add_argument(f"--user-data-dir={user_data_dir}")
|
||||
chrome_options.add_argument(f"--accept-lang={lang}-{lang.upper()},{lang};q=0.9")
|
||||
chrome_options.add_argument("--timezone=Europe/Paris")
|
||||
chrome_options.add_argument("--no-sandbox")
|
||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
||||
chrome_options.add_argument("--mute-audio")
|
||||
chrome_options.add_argument("--disable-notifications")
|
||||
chrome_options.add_argument("--autoplay-policy=user-gesture-required")
|
||||
chrome_options.add_argument("--disable-features=SitePerProcess,IsolateOrigins")
|
||||
chrome_options.add_argument("--enable-features=NetworkService,NetworkServiceInProcess")
|
||||
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
chrome_options.add_argument(f'user-agent={user_agent["ua"]}')
|
||||
resolutions = [(1920, 1080), (1366, 768), (1440, 900)]
|
||||
width, height = random.choice(resolutions)
|
||||
chrome_options.add_argument(f'--window-size={width},{height}')
|
||||
if not stealth_mode:
|
||||
# crx file can't be installed in stealth mode
|
||||
if not os.path.exists(crx_path):
|
||||
pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure")
|
||||
else:
|
||||
@@ -154,16 +155,31 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx
|
||||
stealth(driver,
|
||||
languages=["en-US", "en"],
|
||||
vendor=user_agent["vendor"],
|
||||
platform="Win64" if "Windows" in user_agent["ua"] else "MacIntel" if "Macintosh" in user_agent["ua"] else "Linux x86_64",
|
||||
platform="Win64" if "windows" in user_agent["ua"].lower() else "MacIntel" if "mac" in user_agent["ua"].lower() else "Linux x86_64",
|
||||
webgl_vendor="Intel Inc.",
|
||||
renderer="Intel Iris OpenGL Engine",
|
||||
fix_hairline=True,
|
||||
)
|
||||
return driver
|
||||
security_prefs = {
|
||||
"profile.default_content_setting_values.media_stream": 2,
|
||||
"profile.default_content_setting_values.geolocation": 2,
|
||||
"profile.default_content_setting_values.geolocation": 0,
|
||||
"profile.default_content_setting_values.notifications": 0,
|
||||
"profile.default_content_setting_values.camera": 0,
|
||||
"profile.default_content_setting_values.microphone": 0,
|
||||
"profile.default_content_setting_values.midi_sysex": 0,
|
||||
"profile.default_content_setting_values.clipboard": 0,
|
||||
"profile.default_content_setting_values.media_stream": 0,
|
||||
"profile.default_content_setting_values.background_sync": 0,
|
||||
"profile.default_content_setting_values.sensors": 0,
|
||||
"profile.default_content_setting_values.accessibility_events": 0,
|
||||
"safebrowsing.enabled": True,
|
||||
"credentials_enable_service": False,
|
||||
"profile.password_manager_enabled": False,
|
||||
"webkit.webprefs.accelerated_2d_canvas_enabled": True,
|
||||
"webkit.webprefs.force_dark_mode_enabled": False,
|
||||
"webkit.webprefs.accelerated_2d_canvas_msaa_sample_count": 4,
|
||||
"enable_webgl": True,
|
||||
"enable_webgl2_compute_context": True
|
||||
}
|
||||
chrome_options.add_experimental_option("prefs", security_prefs)
|
||||
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||
@@ -184,12 +200,17 @@ class Browser:
|
||||
except Exception as e:
|
||||
raise Exception(f"Failed to initialize browser: {str(e)}")
|
||||
self.setup_tabs()
|
||||
self.patch_browser_fingerprint()
|
||||
if anticaptcha_manual_install:
|
||||
self.load_anticatpcha_manually()
|
||||
|
||||
def setup_tabs(self):
|
||||
self.tabs = self.driver.window_handles
|
||||
try:
|
||||
self.driver.get("https://www.google.com")
|
||||
except Exception as e:
|
||||
self.logger.log(f"Failed to setup initial tab:" + str(e))
|
||||
pass
|
||||
self.screenshot()
|
||||
|
||||
def switch_control_tab(self):
|
||||
@@ -198,14 +219,40 @@ class Browser:
|
||||
|
||||
def load_anticatpcha_manually(self):
|
||||
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
|
||||
try:
|
||||
self.driver.get(self.anticaptcha)
|
||||
except Exception as e:
|
||||
self.logger.log(f"Failed to setup initial tab:" + str(e))
|
||||
pass
|
||||
|
||||
def human_move(element):
|
||||
actions = ActionChains(driver)
|
||||
x_offset = random.randint(-5,5)
|
||||
for _ in range(random.randint(2,5)):
|
||||
actions.move_by_offset(x_offset, random.randint(-2,2))
|
||||
actions.pause(random.uniform(0.1,0.3))
|
||||
actions.click().perform()
|
||||
|
||||
def human_scroll(self):
|
||||
for _ in range(random.randint(1, 3)):
|
||||
scroll_pixels = random.randint(150, 1200)
|
||||
self.driver.execute_script(f"window.scrollBy(0, {scroll_pixels});")
|
||||
time.sleep(random.uniform(0.5, 2.0))
|
||||
if random.random() < 0.4:
|
||||
self.driver.execute_script(f"window.scrollBy(0, -{random.randint(50, 300)});")
|
||||
time.sleep(random.uniform(0.3, 1.0))
|
||||
|
||||
def patch_browser_fingerprint(self) -> None:
|
||||
script = self.load_js("spoofing.js")
|
||||
self.driver.execute_script(script)
|
||||
|
||||
def go_to(self, url:str) -> bool:
|
||||
"""Navigate to a specified URL."""
|
||||
time.sleep(random.uniform(0.4, 2.5)) # more human behavior
|
||||
time.sleep(random.uniform(0.4, 2.5))
|
||||
try:
|
||||
initial_handles = self.driver.window_handles
|
||||
self.driver.get(url)
|
||||
time.sleep(random.uniform(0.01, 0.3))
|
||||
try:
|
||||
wait = WebDriverWait(self.driver, timeout=10)
|
||||
wait.until(
|
||||
@@ -217,6 +264,8 @@ class Browser:
|
||||
except TimeoutException:
|
||||
self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'")
|
||||
self.apply_web_safety()
|
||||
time.sleep(random.uniform(0.01, 0.2))
|
||||
self.human_scroll()
|
||||
self.logger.log(f"Navigated to: {url}")
|
||||
return True
|
||||
except TimeoutException as e:
|
||||
@@ -616,19 +665,24 @@ class Browser:
|
||||
return self.screenshot_folder + "/updated_screen.png"
|
||||
|
||||
def screenshot(self, filename:str = 'updated_screen.png') -> bool:
|
||||
"""Take a screenshot of the current page."""
|
||||
self.logger.info("Taking screenshot...")
|
||||
"""Take a screenshot of the current page, attempt to capture the full page by zooming out."""
|
||||
self.logger.info("Taking full page screenshot...")
|
||||
time.sleep(0.1)
|
||||
try:
|
||||
original_zoom = self.driver.execute_script("return document.body.style.zoom || 1;")
|
||||
self.driver.execute_script("document.body.style.zoom='75%'")
|
||||
time.sleep(0.1)
|
||||
path = os.path.join(self.screenshot_folder, filename)
|
||||
if not os.path.exists(self.screenshot_folder):
|
||||
os.makedirs(self.screenshot_folder)
|
||||
self.driver.save_screenshot(path)
|
||||
self.logger.info(f"Screenshot saved as {filename}")
|
||||
return True
|
||||
self.logger.info(f"Full page screenshot saved as {filename}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error taking screenshot: {str(e)}")
|
||||
self.logger.error(f"Error taking full page screenshot: {str(e)}")
|
||||
return False
|
||||
finally:
|
||||
self.driver.execute_script(f"document.body.style.zoom='1'")
|
||||
return True
|
||||
|
||||
def apply_web_safety(self):
|
||||
"""
|
||||
@@ -639,17 +693,27 @@ class Browser:
|
||||
input_elements = self.driver.execute_script(script)
|
||||
|
||||
if __name__ == "__main__":
|
||||
driver = create_driver(headless=False, stealth_mode=True)
|
||||
driver = create_driver(headless=False, stealth_mode=True, crx_path="../crx/nopecha.crx")
|
||||
browser = Browser(driver, anticaptcha_manual_install=True)
|
||||
|
||||
input("press enter to continue")
|
||||
print("AntiCaptcha / Form Test")
|
||||
#browser.go_to("https://www.browserscan.net/bot-detection")
|
||||
browser.go_to("https://www.google.com/recaptcha/api2/demo")
|
||||
time.sleep(50)
|
||||
browser.go_to("https://bot.sannysoft.com")
|
||||
time.sleep(5)
|
||||
#txt = browser.get_text()
|
||||
#browser.go_to("https://www.google.com/recaptcha/api2/demo")
|
||||
browser.go_to("https://home.openweathermap.org/users/sign_up")
|
||||
inputs_visible = browser.get_form_inputs()
|
||||
print("inputs:", inputs_visible)
|
||||
#inputs_fill = ['[q](checked)', '[q](checked)', '[user[username]](mlg)', '[user[email]](mlg.fcu@gmail.com)', '[user[password]](placeholder_P@ssw0rd123)', '[user[password_confirmation]](placeholder_P@ssw0rd123)']
|
||||
#browser.fill_form(inputs_fill)
|
||||
input("press enter to exit")
|
||||
|
||||
# Test sites for browser fingerprinting and captcha
|
||||
# https://nowsecure.nl/
|
||||
# https://bot.sannysoft.com
|
||||
# https://browserleaks.com/
|
||||
# https://bot.incolumitas.com/
|
||||
# https://fingerprintjs.github.io/fingerprintjs/
|
||||
# https://antoinevastel.com/bots/
|
||||
@@ -22,6 +22,7 @@ class Interaction:
|
||||
self.current_agent = None
|
||||
self.last_query = None
|
||||
self.last_answer = None
|
||||
self.last_reasoning = None
|
||||
self.agents = agents
|
||||
self.tts_enabled = tts_enabled
|
||||
self.stt_enabled = stt_enabled
|
||||
@@ -158,7 +159,7 @@ class Interaction:
|
||||
tmp = self.last_answer
|
||||
self.current_agent = agent
|
||||
self.is_generating = True
|
||||
self.last_answer, _ = await agent.process(self.last_query, self.speech)
|
||||
self.last_answer, self.last_reasoning = await agent.process(self.last_query, self.speech)
|
||||
self.is_generating = False
|
||||
if push_last_agent_memory:
|
||||
self.current_agent.memory.push('user', self.last_query)
|
||||
|
||||
+90
-25
@@ -1,20 +1,18 @@
|
||||
|
||||
import os
|
||||
import time
|
||||
import ollama
|
||||
from ollama import chat
|
||||
import requests
|
||||
import subprocess
|
||||
import ipaddress
|
||||
import httpx
|
||||
import socket
|
||||
import platform
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
from dotenv import load_dotenv, set_key
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from ollama import Client as OllamaClient
|
||||
from openai import OpenAI
|
||||
from typing import List, Tuple, Type, Dict
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
|
||||
from sources.logger import Logger
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
|
||||
class Provider:
|
||||
def __init__(self, provider_name, model, server_address="127.0.0.1:5000", is_local=False):
|
||||
@@ -22,6 +20,7 @@ class Provider:
|
||||
self.model = model
|
||||
self.is_local = is_local
|
||||
self.server_ip = server_address
|
||||
self.server_address = server_address
|
||||
self.available_providers = {
|
||||
"ollama": self.ollama_fn,
|
||||
"server": self.server_fn,
|
||||
@@ -32,11 +31,12 @@ class Provider:
|
||||
"deepseek": self.deepseek_fn,
|
||||
"together": self.together_fn,
|
||||
"dsk_deepseek": self.dsk_deepseek,
|
||||
"openrouter": self.openrouter_fn,
|
||||
"test": self.test_fn
|
||||
}
|
||||
self.logger = Logger("provider.log")
|
||||
self.api_key = None
|
||||
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google"]
|
||||
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google", "openrouter"]
|
||||
if self.provider_name not in self.available_providers:
|
||||
raise ValueError(f"Unknown provider: {provider_name}")
|
||||
if self.provider_name in self.unsafe_providers and self.is_local == False:
|
||||
@@ -73,7 +73,8 @@ class Provider:
|
||||
except AttributeError as e:
|
||||
raise NotImplementedError(f"{str(e)}\nIs {self.provider_name} implemented ?")
|
||||
except ModuleNotFoundError as e:
|
||||
raise ModuleNotFoundError(f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
|
||||
raise ModuleNotFoundError(
|
||||
f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
|
||||
except Exception as e:
|
||||
if "try again later" in str(e).lower():
|
||||
return f"{self.provider_name} server is overloaded. Please try again later."
|
||||
@@ -106,7 +107,6 @@ class Provider:
|
||||
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e:
|
||||
return False
|
||||
|
||||
|
||||
def server_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use a remote server with LLM to generate text.
|
||||
@@ -141,36 +141,45 @@ class Provider:
|
||||
pretty_print(f"An error occurred: {str(e)}", color="failure")
|
||||
break
|
||||
except KeyError as e:
|
||||
raise Exception(f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
|
||||
raise Exception(
|
||||
f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
|
||||
except Exception as e:
|
||||
raise e
|
||||
return thought
|
||||
|
||||
def ollama_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use local ollama server to generate text.
|
||||
Use local or remote Ollama server to generate text.
|
||||
"""
|
||||
thought = ""
|
||||
host = "http://localhost:11434" if self.is_local else f"http://{self.server_address}"
|
||||
client = OllamaClient(host=host)
|
||||
|
||||
try:
|
||||
stream = chat(
|
||||
stream = client.chat(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
stream=True,
|
||||
)
|
||||
for chunk in stream:
|
||||
if verbose:
|
||||
print(chunk['message']['content'], end='', flush=True)
|
||||
thought += chunk['message']['content']
|
||||
print(chunk["message"]["content"], end="", flush=True)
|
||||
thought += chunk["message"]["content"]
|
||||
except httpx.ConnectError as e:
|
||||
raise Exception("\nOllama connection failed. provider should not be set to ollama if server address is not localhost") from e
|
||||
except ollama.ResponseError as e:
|
||||
if e.status_code == 404:
|
||||
raise Exception(
|
||||
f"\nOllama connection failed at {host}. Check if the server is running."
|
||||
) from e
|
||||
except Exception as e:
|
||||
if hasattr(e, 'status_code') and e.status_code == 404:
|
||||
animate_thinking(f"Downloading {self.model}...")
|
||||
ollama.pull(self.model)
|
||||
client.pull(self.model)
|
||||
self.ollama_fn(history, verbose)
|
||||
if "refused" in str(e).lower():
|
||||
raise Exception("Ollama connection failed. is the server running ?") from e
|
||||
raise Exception(
|
||||
f"Ollama connection refused at {host}. Is the server running?"
|
||||
) from e
|
||||
raise e
|
||||
|
||||
return thought
|
||||
|
||||
def huggingface_fn(self, history, verbose=False):
|
||||
@@ -213,6 +222,38 @@ class Provider:
|
||||
except Exception as e:
|
||||
raise Exception(f"OpenAI API error: {str(e)}") from e
|
||||
|
||||
def anthropic_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use Anthropic to generate text.
|
||||
"""
|
||||
from anthropic import Anthropic
|
||||
|
||||
client = Anthropic(api_key=self.api_key)
|
||||
system_message = None
|
||||
messages = []
|
||||
for message in history:
|
||||
clean_message = {'role': message['role'], 'content': message['content']}
|
||||
if message['role'] == 'system':
|
||||
system_message = message['content']
|
||||
else:
|
||||
messages.append(clean_message)
|
||||
|
||||
try:
|
||||
response = client.messages.create(
|
||||
model=self.model,
|
||||
max_tokens=1024,
|
||||
messages=messages,
|
||||
system=system_message
|
||||
)
|
||||
if response is None:
|
||||
raise Exception("Anthropic response is empty.")
|
||||
thought = response.content[0].text
|
||||
if verbose:
|
||||
print(thought)
|
||||
return thought
|
||||
except Exception as e:
|
||||
raise Exception(f"Anthropic API error: {str(e)}") from e
|
||||
|
||||
def google_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use google gemini to generate text.
|
||||
@@ -304,6 +345,29 @@ class Provider:
|
||||
raise Exception(f"An error occurred: {str(e)}") from e
|
||||
return thought
|
||||
|
||||
def openrouter_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use OpenRouter API to generate text.
|
||||
"""
|
||||
client = OpenAI(api_key=self.api_key, base_url="https://openrouter.ai/api/v1")
|
||||
if self.is_local:
|
||||
# This case should ideally not be reached if unsafe_providers is set correctly
|
||||
# and is_local is False in config for openrouter
|
||||
raise Exception("OpenRouter is not available for local use. Change config.ini")
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
)
|
||||
if response is None:
|
||||
raise Exception("OpenRouter response is empty.")
|
||||
thought = response.choices[0].message.content
|
||||
if verbose:
|
||||
print(thought)
|
||||
return thought
|
||||
except Exception as e:
|
||||
raise Exception(f"OpenRouter API error: {str(e)}") from e
|
||||
|
||||
def dsk_deepseek(self, history, verbose=False):
|
||||
"""
|
||||
Use: xtekky/deepseek4free
|
||||
@@ -349,6 +413,7 @@ class Provider:
|
||||
"""
|
||||
return thought
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
provider = Provider("server", "deepseek-r1:32b", " x.x.x.x:8080")
|
||||
res = provider.respond(["user", "Hello, how are you?"])
|
||||
|
||||
+3
-1
@@ -17,12 +17,14 @@ class Logger:
|
||||
def create_logging(self, log_filename):
|
||||
self.logger = logging.getLogger(log_filename)
|
||||
self.logger.setLevel(logging.DEBUG)
|
||||
if not self.logger.handlers:
|
||||
self.logger.handlers.clear()
|
||||
self.logger.propagate = False
|
||||
file_handler = logging.FileHandler(self.log_path)
|
||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
file_handler.setFormatter(formatter)
|
||||
self.logger.addHandler(file_handler)
|
||||
|
||||
|
||||
def create_folder(self, path):
|
||||
"""Create log dir"""
|
||||
try:
|
||||
|
||||
+4
-4
@@ -118,13 +118,13 @@ class Memory():
|
||||
json_memory = json.load(f)
|
||||
except FileNotFoundError:
|
||||
self.logger.warning(f"File not found: {path}")
|
||||
return None
|
||||
return {}
|
||||
except json.JSONDecodeError:
|
||||
self.logger.warning(f"Error decoding JSON from file: {path}")
|
||||
return None
|
||||
return {}
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error loading file {path}: {e}")
|
||||
return None
|
||||
return {}
|
||||
return json_memory
|
||||
|
||||
def load_memory(self, agent_type: str = "casual_agent") -> None:
|
||||
@@ -238,7 +238,7 @@ class Memory():
|
||||
if len(self.memory[i]['content']) > 1024:
|
||||
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
|
||||
|
||||
def trip_text_to_max_ctx(self, text: str) -> str:
|
||||
def trim_text_to_max_ctx(self, text: str) -> str:
|
||||
"""
|
||||
Truncate a text to fit within the maximum context size of the model.
|
||||
"""
|
||||
|
||||
@@ -19,6 +19,7 @@ class QueryRequest(BaseModel):
|
||||
class QueryResponse(BaseModel):
|
||||
done: str
|
||||
answer: str
|
||||
reasoning: str
|
||||
agent_name: str
|
||||
success: str
|
||||
blocks: dict
|
||||
@@ -32,6 +33,7 @@ class QueryResponse(BaseModel):
|
||||
return {
|
||||
"done": self.done,
|
||||
"answer": self.answer,
|
||||
"reasoning": self.reasoning,
|
||||
"agent_name": self.agent_name,
|
||||
"success": self.success,
|
||||
"blocks": self.blocks,
|
||||
|
||||
@@ -8,7 +8,7 @@ if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
from sources.tools.safety import is_unsafe
|
||||
from sources.tools.safety import is_any_unsafe
|
||||
|
||||
class BashInterpreter(Tools):
|
||||
"""
|
||||
@@ -43,9 +43,9 @@ class BashInterpreter(Tools):
|
||||
for command in commands:
|
||||
command = f"cd {self.work_dir} && {command}"
|
||||
command = command.replace('\n', '')
|
||||
if self.safe_mode and is_unsafe(commands):
|
||||
if self.safe_mode and is_any_unsafe(commands):
|
||||
print(f"Unsafe command rejected: {command}")
|
||||
return "Unsafe command detected, execution aborted."
|
||||
return "\nUnsafe command: {command}. Execution aborted. This is beyond allowed capabilities report to user."
|
||||
if self.language_bash_attempt(command) and self.allow_language_exec_bash == False:
|
||||
continue
|
||||
try:
|
||||
@@ -100,6 +100,7 @@ class BashInterpreter(Tools):
|
||||
r"not permitted",
|
||||
r"not installed",
|
||||
r"not found",
|
||||
r"aborted",
|
||||
r"no such",
|
||||
r"too many",
|
||||
r"too few",
|
||||
|
||||
+10
-1
@@ -31,7 +31,7 @@ unsafe_commands_unix = [
|
||||
"route" # Routing table management
|
||||
"--force", # Force flag for many commands
|
||||
"rebase", # Rebase git repository
|
||||
"git ." # Git commands
|
||||
"git" # Git commands
|
||||
]
|
||||
|
||||
unsafe_commands_windows = [
|
||||
@@ -66,6 +66,15 @@ unsafe_commands_windows = [
|
||||
"bootcfg"
|
||||
]
|
||||
|
||||
def is_any_unsafe(cmds):
|
||||
"""
|
||||
check if any bash command is unsafe.
|
||||
"""
|
||||
for cmd in cmds:
|
||||
if is_unsafe(cmd):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_unsafe(cmd):
|
||||
"""
|
||||
check if a bash command is unsafe.
|
||||
|
||||
@@ -21,7 +21,5 @@ window.fetch = function() {
|
||||
console.log('Blocked fetch request');
|
||||
return Promise.reject('Blocked');
|
||||
};
|
||||
// Block annoying dialogs
|
||||
window.alert = function() {};
|
||||
window.confirm = function() { return false; };
|
||||
|
||||
window.prompt = function() { return null; };
|
||||
@@ -0,0 +1,126 @@
|
||||
|
||||
// Core automation masking
|
||||
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array;
|
||||
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise;
|
||||
delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol;
|
||||
|
||||
window.RTCPeerConnection = undefined;
|
||||
window.webkitRTCPeerConnection = undefined;
|
||||
window.mozRTCPeerConnection = undefined;
|
||||
|
||||
window.Notification = class Notification {
|
||||
constructor(title, options = {}) {
|
||||
this.title = title;
|
||||
this.options = options;
|
||||
}
|
||||
static permission = 'granted';
|
||||
static requestPermission = () => Promise.resolve('granted');
|
||||
close() {}
|
||||
onclick = null;
|
||||
onerror = null;
|
||||
onclose = null;
|
||||
onshow = null;
|
||||
};
|
||||
|
||||
Object.keys(window).forEach((key) => {
|
||||
if (key.includes("webdriver") || key.includes("selenium") || key.includes("driver")) {
|
||||
delete window[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Randomize plugins
|
||||
|
||||
const pluginsList = [
|
||||
{type: 'application/x-google-chrome-pdf', description: 'Portable Document Format', filename: 'internal-pdf-viewer', name: 'Chrome PDF Plugin'},
|
||||
{type: 'application/x-nacl', description: 'Native Client Executable', filename: 'internal-nacl-plugin', name: 'Native Client'},
|
||||
{type: 'application/x-ppapi-widevine-cdm', description: 'Widevine Content Decryption Module', filename: 'widevinecdm', name: 'Widevine CDM'}
|
||||
];
|
||||
Object.defineProperty(navigator, 'plugins', {
|
||||
get: () => pluginsList.slice(0, Math.floor(Math.random() * pluginsList.length) + 1)
|
||||
});
|
||||
|
||||
// Font spoofing
|
||||
|
||||
const fontList = ['Arial', 'Helvetica', 'Times New Roman', 'Courier New', 'Verdana'];
|
||||
Object.defineProperty(document, 'fonts', {
|
||||
value: {
|
||||
add: function() {},
|
||||
check: function(font) { return fontList.includes(font.split(' ').slice(-1)[0]); },
|
||||
delete: function() {},
|
||||
forEach: function(cb) { fontList.forEach(f => cb(f)); },
|
||||
has: function(font) { return fontList.includes(font.split(' ').slice(-1)[0]); },
|
||||
keys: function() { return fontList; },
|
||||
size: fontList.length
|
||||
}
|
||||
});
|
||||
|
||||
// Canvas fingerprint spoofing
|
||||
|
||||
HTMLCanvasElement.prototype.toDataURL = function() {
|
||||
const ctx = this.getContext('2d');
|
||||
// Add varied noise to avoid consistent fingerprints
|
||||
for (let i = 0; i < 10; i++) {
|
||||
ctx.fillStyle = `rgba(${Math.random() * 5}, ${Math.random() * 5}, ${Math.random() * 5}, 0.005)`;
|
||||
ctx.fillRect(Math.random() * this.width, Math.random() * this.height, 1, 1);
|
||||
}
|
||||
return originalToDataURL.apply(this, arguments);
|
||||
};
|
||||
|
||||
const [w, h] = [1920, 1080];
|
||||
Object.defineProperty(window, 'screen', {
|
||||
value: {
|
||||
width: w,
|
||||
height: h,
|
||||
availWidth: w - 20,
|
||||
availHeight: h - 100,
|
||||
colorDepth: 24,
|
||||
pixelDepth: 24
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// ===== WebGL Consistency =====
|
||||
const os = navigator.userAgent.includes('Windows') ? 'Windows' : 'Mac';
|
||||
const webGLParams = {
|
||||
'Windows': {
|
||||
37445: 'Google Inc. (NVIDIA)', // VENDOR
|
||||
37446: 'ANGLE (NVIDIA, NVIDIA GeForce RTX 3060)', // RENDERER
|
||||
36349: 'NVIDIA Corporation', // UNMASKED_VENDOR_WEBGL
|
||||
37444: 'NVIDIA GeForce RTX 3060', // UNMASKED_RENDERER_WEBGL
|
||||
35661: 'WebGL 2.0' // VERSION
|
||||
},
|
||||
'Mac': {
|
||||
37445: 'Apple Inc.',
|
||||
37446: 'Apple M1 Pro',
|
||||
36349: 'Apple',
|
||||
37444: 'Apple M1 Pro',
|
||||
35661: 'WebGL 2.0 (Metal)'
|
||||
}
|
||||
};
|
||||
|
||||
// replace WebGL parameters
|
||||
WebGLRenderingContext.prototype.getParameter = function(parameter) {
|
||||
return webGLParams[os][parameter] || getParameter.call(this, parameter);
|
||||
};
|
||||
|
||||
// Performance API spoofing
|
||||
if ('performance' in window) {
|
||||
Object.defineProperty(performance, 'memory', {
|
||||
value: {
|
||||
jsHeapSizeLimit: 4294705152,
|
||||
totalJSHeapSize: 78365432,
|
||||
usedJSHeapSize: 46543210
|
||||
},
|
||||
configurable: true
|
||||
});
|
||||
}
|
||||
|
||||
const originalCreate = window.AudioContext || window.webkitAudioContext;
|
||||
window.AudioContext = window.webkitAudioContext = function() {
|
||||
const context = new originalCreate();
|
||||
const analyser = context.createAnalyser();
|
||||
analyser.fake = true; // Mark as spoofed
|
||||
// Spoof common methods
|
||||
analyser.getFloatFrequencyData = () => new Float32Array(1024).fill(Math.random() * -100);
|
||||
return context;
|
||||
};
|
||||
@@ -1,8 +1,5 @@
|
||||
@echo off
|
||||
|
||||
REM Up the provider in windows
|
||||
start ollama serve
|
||||
|
||||
docker-compose up
|
||||
if %ERRORLEVEL% neq 0 (
|
||||
echo Error: Failed to start containers. Check Docker logs with 'docker compose logs'.
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class TestToolsParsing(unittest.TestCase):
|
||||
"""
|
||||
Test suite for the Tools class parsing functionality, specifically the load_exec_block method.
|
||||
This method is responsible for extracting code blocks from LLM-generated text.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures before each test method."""
|
||||
class TestTool(Tools):
|
||||
def execute(self, blocks, safety=False):
|
||||
return "test execution"
|
||||
|
||||
def execution_failure_check(self, output):
|
||||
return False
|
||||
|
||||
def interpreter_feedback(self, output):
|
||||
return "test feedback"
|
||||
|
||||
self.tool = TestTool()
|
||||
self.tool.tag = "python" # Set tag for testing
|
||||
|
||||
def test_load_exec_block_single_block(self):
|
||||
"""Test parsing a single code block from LLM text."""
|
||||
llm_text = """Here's some Python code:
|
||||
```python
|
||||
print("Hello, World!")
|
||||
x = 42
|
||||
```
|
||||
That's the code."""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 1)
|
||||
self.assertEqual(blocks[0], '\nprint("Hello, World!")\nx = 42\n')
|
||||
self.assertIsNone(save_path)
|
||||
|
||||
def test_load_exec_block_multiple_blocks(self):
|
||||
"""Test parsing multiple code blocks from LLM text."""
|
||||
llm_text = """First block:
|
||||
```python
|
||||
import os
|
||||
print("First block")
|
||||
```
|
||||
|
||||
Second block:
|
||||
```python
|
||||
import sys
|
||||
print("Second block")
|
||||
```
|
||||
|
||||
Done."""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 2)
|
||||
self.assertEqual(blocks[0], '\nimport os\nprint("First block")\n')
|
||||
self.assertEqual(blocks[1], '\nimport sys\nprint("Second block")\n')
|
||||
self.assertIsNone(save_path)
|
||||
|
||||
def test_load_exec_block_with_save_path(self):
|
||||
"""Test parsing code block with save path specification."""
|
||||
llm_text = """```python
|
||||
save_path: test_file.py
|
||||
import os
|
||||
print("Hello with save path")
|
||||
```"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 1)
|
||||
self.assertEqual(blocks[0], '\nsave_path: test_file.py\nimport os\nprint("Hello with save path")\n')
|
||||
self.assertIsNone(save_path)
|
||||
|
||||
|
||||
def test_load_exec_block_with_indentation(self):
|
||||
"""Test parsing code blocks with leading whitespace/indentation."""
|
||||
llm_text = """ Here's indented code:
|
||||
```python
|
||||
def hello():
|
||||
print("Hello")
|
||||
return True
|
||||
```
|
||||
End of code."""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 1)
|
||||
expected_code = '\ndef hello():\n print("Hello")\n return True\n'
|
||||
self.assertEqual(blocks[0], expected_code)
|
||||
|
||||
def test_load_exec_block_no_blocks(self):
|
||||
"""Test parsing text with no code blocks."""
|
||||
llm_text = """This is just regular text with no code blocks.
|
||||
There are no python blocks here.
|
||||
Just plain text."""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNone(blocks)
|
||||
self.assertIsNone(save_path)
|
||||
|
||||
def test_load_exec_block_wrong_tag(self):
|
||||
"""Test parsing text with code blocks but wrong language tag."""
|
||||
llm_text = """```javascript
|
||||
console.log("This is JavaScript, not Python");
|
||||
```"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNone(blocks)
|
||||
self.assertIsNone(save_path)
|
||||
|
||||
def test_load_exec_block_incomplete_block(self):
|
||||
"""Test parsing text with incomplete code block (missing closing tag)."""
|
||||
llm_text = """```python
|
||||
print("This block has no closing tag")
|
||||
x = 42"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertEqual(blocks, [])
|
||||
self.assertIsNone(save_path)
|
||||
|
||||
def test_load_exec_block_empty_block(self):
|
||||
"""Test parsing empty code block."""
|
||||
llm_text = """```python
|
||||
```"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 1)
|
||||
self.assertEqual(blocks[0], '\n')
|
||||
|
||||
def test_load_exec_block_mixed_content(self):
|
||||
"""Test parsing text with mixed content including code blocks."""
|
||||
llm_text = """Let me help you with that task.
|
||||
|
||||
First, I'll import the necessary modules:
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
```
|
||||
|
||||
Then I'll define a function:
|
||||
```python
|
||||
def process_data(data):
|
||||
return data.upper()
|
||||
```
|
||||
|
||||
Finally, let's use it:
|
||||
```python
|
||||
result = process_data("hello world")
|
||||
print(result)
|
||||
```
|
||||
|
||||
That should work!"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 3)
|
||||
self.assertEqual(blocks[0], '\nimport os\nimport sys\n')
|
||||
self.assertEqual(blocks[1], '\ndef process_data(data):\n return data.upper()\n')
|
||||
self.assertEqual(blocks[2], '\nresult = process_data("hello world")\nprint(result)\n')
|
||||
|
||||
def test_load_exec_block_with_special_characters(self):
|
||||
"""Test parsing code blocks containing special characters."""
|
||||
llm_text = """```python
|
||||
text = "Hello \"world\" with 'quotes'"
|
||||
regex = r"^\\d+$"
|
||||
path = "C:\\Users\\test\\file.txt"
|
||||
```"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertIsNotNone(blocks)
|
||||
self.assertEqual(len(blocks), 1)
|
||||
expected = '\ntext = "Hello "world" with \'quotes\'"\nregex = r"^\\d+$"\npath = "C:\\Users\\test\\file.txt"\n'
|
||||
self.assertEqual(blocks[0], expected)
|
||||
|
||||
def test_load_exec_block_tag_undefined(self):
|
||||
"""Test that assertion error is raised when tag is undefined."""
|
||||
self.tool.tag = "undefined"
|
||||
llm_text = """```python
|
||||
print("test")
|
||||
```"""
|
||||
|
||||
with self.assertRaises(AssertionError):
|
||||
self.tool.load_exec_block(llm_text)
|
||||
|
||||
def test_found_executable_blocks_flag(self):
|
||||
"""Test that the executable blocks found flag is set correctly."""
|
||||
self.assertFalse(self.tool.found_executable_blocks())
|
||||
|
||||
llm_text = """```python
|
||||
print("test")
|
||||
```"""
|
||||
|
||||
blocks, save_path = self.tool.load_exec_block(llm_text)
|
||||
|
||||
self.assertTrue(self.tool.found_executable_blocks())
|
||||
|
||||
self.assertFalse(self.tool.found_executable_blocks())
|
||||
|
||||
def test_get_parameter_value(self):
|
||||
"""Test the get_parameter_value helper method."""
|
||||
block = """param1 = value1
|
||||
param2 = value2
|
||||
some other text
|
||||
param3 = value3"""
|
||||
|
||||
self.assertEqual(self.tool.get_parameter_value(block, "param1"), "value1")
|
||||
self.assertEqual(self.tool.get_parameter_value(block, "param2"), "value2")
|
||||
self.assertEqual(self.tool.get_parameter_value(block, "param3"), "value3")
|
||||
self.assertIsNone(self.tool.get_parameter_value(block, "nonexistent"))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user