Compare commits
107
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 | ||
|
|
101c103aeb | ||
|
|
de315a43a1 | ||
|
|
90f173ba52 | ||
|
|
e4591ea1b4 | ||
|
|
a7deffedec | ||
|
|
5949540007 | ||
|
|
7afb79117b | ||
|
|
442bb4a340 | ||
|
|
99467be133 | ||
|
|
887060acdf | ||
|
|
90609e960c | ||
|
|
d893928221 | ||
|
|
5bc086fd9d | ||
|
|
aca176b9e7 | ||
|
|
9707dbcbf9 | ||
|
|
52e5af8116 | ||
|
|
42058244f2 | ||
|
|
bddaa75e8c | ||
|
|
7904439f35 | ||
|
|
c873af3d00 | ||
|
|
fa2852d3e7 | ||
|
|
1c4ebefae4 | ||
|
|
96a6dd368a | ||
|
|
ed4f04b19c | ||
|
|
f325865869 | ||
|
|
a15dd998f3 | ||
|
|
f17dc0550b | ||
|
|
ed76c8415b | ||
|
|
9f2c105074 | ||
|
|
3cf1cab68f | ||
|
|
0579fd3bb6 | ||
|
|
c6688355a7 | ||
|
|
68ed1834a9 |
@@ -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.
|
||||
|
||||
@@ -58,6 +62,10 @@ source agentic_seek_env/bin/activate
|
||||
|
||||
### 3️⃣ **Install package**
|
||||
|
||||
Ensure Python, Docker and docker compose, and Google chrome are installed.
|
||||
|
||||
We recommand Python 3.10.0.
|
||||
|
||||
**Automatic Installation (Recommanded):**
|
||||
|
||||
For Linux/Macos:
|
||||
@@ -66,48 +74,59 @@ For Linux/Macos:
|
||||
```
|
||||
|
||||
For windows:
|
||||
|
||||
```sh
|
||||
./install.bat
|
||||
```
|
||||
|
||||
**Manually:**
|
||||
|
||||
First, you need to install these packages:
|
||||
**Note: For any OS, ensure the ChromeDriver you install matches your installed Chrome version. Run `google-chrome --version`. See known issues if you have chrome >135**
|
||||
|
||||
- *Linux*:
|
||||
|
||||
Updates package list (apt-get update).
|
||||
Update Package List: `sudo apt update`
|
||||
|
||||
Install these:
|
||||
alsa-utils, portaudio19-dev, python3-pyaudio, libgtk-3-dev, libnotify-dev, libgconf-2-4, libnss3, libxss1, selenium
|
||||
Install Dependencies: `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||
|
||||
Make sure to install docker + docker-compose if not already.
|
||||
Install ChromeDriver matching your Chrome browser version:
|
||||
`sudo apt install -y chromium-chromedriver`
|
||||
|
||||
Install requirements: `pip3 install -r requirements.txt`
|
||||
|
||||
- *Macos*:
|
||||
|
||||
Update package list.
|
||||
Install chromedriver.
|
||||
Install portaudio.
|
||||
Install chromedriver and selenium.
|
||||
Update brew : `brew update`
|
||||
|
||||
Install chromedriver : `brew install --cask chromedriver`
|
||||
|
||||
Install portaudio: `brew install portaudio`
|
||||
|
||||
Upgrade pip : `python3 -m pip install --upgrade pip`
|
||||
|
||||
Upgrade wheel : : `pip3 install --upgrade setuptools wheel`
|
||||
|
||||
Install requirements: `pip3 install -r requirements.txt`
|
||||
|
||||
- *Windows*:
|
||||
|
||||
Install pyreadline3, selenium portaudio, pyAudio and chromedriver
|
||||
Install pyreadline3 `pip install pyreadline3`
|
||||
|
||||
Then install pip requirements:
|
||||
Install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: `pip install pyaudio`
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# or
|
||||
python3 setup.py install
|
||||
```
|
||||
Download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started
|
||||
|
||||
Place chromedriver in a directory included in your PATH.
|
||||
|
||||
Install requirements: `pip3 install -r requirements.txt`
|
||||
|
||||
---
|
||||
|
||||
## 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**
|
||||
|
||||
@@ -121,26 +140,40 @@ See below for a list of local supported provider.
|
||||
|
||||
**Update the config.ini**
|
||||
|
||||
Change the config.ini file to set the provider_name to a supported provider and provider_model to `deepseek-r1:14b`
|
||||
Change the config.ini file to set the provider_name to a supported provider and provider_model to a LLM supported by your provider. We recommand reasoning model such as *Qwen* or *Deepseek*.
|
||||
|
||||
NOTE: `deepseek-r1:14b`is an example, use a bigger model if your hardware allow it.
|
||||
See the **FAQ** at the end of the README for required hardware.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
is_local = True # Whenever you are running locally or with remote provider.
|
||||
provider_name = ollama # or lm-studio, openai, etc..
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_model = deepseek-r1:14b # choose a model that fit your hardware
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Jarvis # name of your AI
|
||||
recover_last_session = True # whenever to recover the previous session
|
||||
save_session = True # whenever to remember the current session
|
||||
speak = True # text to speech
|
||||
listen = False # Speech to text, only for CLI
|
||||
work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek.
|
||||
jarvis_personality = False # Whenever to use a more "Jarvis" like personality (experimental)
|
||||
languages = en zh # The list of languages, Text to speech will default to the first language on the list
|
||||
[BROWSER]
|
||||
headless_browser = True # Whenever to use headless browser, recommanded only if you use web interface.
|
||||
stealth_mode = True # Use undetected selenium to reduce browser detection
|
||||
```
|
||||
|
||||
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**
|
||||
|
||||
| Provider | Local? | Description |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
||||
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
|
||||
| openai | Yes | Use openai compatible API |
|
||||
|
||||
| openai | Yes | Use openai compatible API (eg: llama.cpp server) |
|
||||
|
||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
@@ -159,9 +192,9 @@ Set the desired provider in the `config.ini`. See below for a list of API provid
|
||||
```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 # doesn't matter
|
||||
```
|
||||
Warning: Make sure there is not trailing space in the config.
|
||||
|
||||
@@ -174,11 +207,15 @@ 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) |
|
||||
|
||||
*We advice against using gpt-4o or other closedAI models*, performance are poor for web browsing and task planning.
|
||||
|
||||
Please also note that coding/bash might fail with gemini, it seem to ignore our prompt for format to respect, which are optimized for deepseek r1.
|
||||
|
||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||
|
||||
*See the **Known issues** section if you are having issues*
|
||||
@@ -210,6 +247,8 @@ start ./start_services.cmd # Window
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
We advice you set `headless_browser` to False in the config.ini for CLI mode.
|
||||
|
||||
**Options 2:** Run with the Web interface.
|
||||
|
||||
Start the backend.
|
||||
@@ -232,37 +271,22 @@ To exit, simply say/type `goodbye`.
|
||||
|
||||
Here are some example usage:
|
||||
|
||||
### Coding/Bash
|
||||
> *Make a snake game in python!*
|
||||
|
||||
> *Make a snake game in python*
|
||||
> *Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.*
|
||||
|
||||
> *Show me how to multiply matrice in C*
|
||||
> *Write a Go program to calculate the factorial of a number, save it as factorial.go in your workspace*
|
||||
|
||||
> *Make a blackjack in golang*
|
||||
> *Search my summer_pictures folder for all JPG files, rename them with today’s date, and save a list of renamed files in photos_list.txt*
|
||||
|
||||
### Web search
|
||||
> *Search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt.*
|
||||
|
||||
> *Do a web search to find cool tech startup in Japan working on cutting edge AI research*
|
||||
> *Search the web for the latest AI news articles from 2025, select three, and write a Python script to scrape their titles and summaries. Save the script as news_scraper.py and the summaries in ai_news.txt in /home/projects*
|
||||
|
||||
> *Can you find on the internet who created AgenticSeek?*
|
||||
> *Friday, search the web for a free stock price API, register with supersuper7434567@gmail.com then write a Python script to fetch using the API daily prices for Tesla, and save the results in stock_prices.csv*
|
||||
|
||||
> *Can you use a fuel calculator online to estimate the cost of a Nice - Milan trip*
|
||||
*Note that form filling capabilities are still experimental and might fail.*
|
||||
|
||||
### File system
|
||||
|
||||
> *Hey can you find where is contract.pdf i lost it*
|
||||
|
||||
> *Show me how much space I have left on my disk*
|
||||
|
||||
> *Can you follow the readme and install project at /home/path/project*
|
||||
|
||||
### Casual
|
||||
|
||||
> *Tell me about Rennes, France*
|
||||
|
||||
> *Should I pursue a phd ?*
|
||||
|
||||
> *What's the best workout routine ?*
|
||||
|
||||
|
||||
After you type your query, AgenticSeek will allocate the best agent for the task.
|
||||
@@ -297,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:
|
||||
@@ -470,6 +494,27 @@ And download the chromedriver version matching your OS.
|
||||
|
||||
If this section is incomplete please raise an issue.
|
||||
|
||||
## connection adapters Issues
|
||||
|
||||
```
|
||||
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions'
|
||||
```
|
||||
|
||||
Make sure you have `http://` in front of the provider IP address :
|
||||
|
||||
`provider_server_address = http://127.0.0.1:11434`
|
||||
|
||||
## SearxNG base URL must be provided
|
||||
|
||||
```
|
||||
raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.")
|
||||
ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.
|
||||
```
|
||||
|
||||
Maybe you didn't move `.env.example` as `.env` ? You can also export SEARXNG_BASE_URL:
|
||||
|
||||
`export SEARXNG_BASE_URL="http://127.0.0.1:8080"`
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: What hardware do I need?**
|
||||
@@ -508,5 +553,9 @@ We’re looking for developers to improve AgenticSeek! Check out open issues or
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
|
||||
## Maintainers:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | Paris Time
|
||||
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | Taipei Time
|
||||
|
||||
+97
-41
@@ -1,43 +1,32 @@
|
||||
# AgenticSeek: Private, Local Manus Alternative.
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<p>
|
||||
|
||||
|
||||
[English](./README.md) | 中文 | [日本語](./README_JP.md)
|
||||
|
||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
||||
|
||||
**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页,并自动修正错误与反省,最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。
|
||||
*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。*
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
|
||||
### 为什么选择 AgenticSeek?
|
||||
|
||||
* 🔒 完全本地化与隐私保护 - 所有功能都在您的设备上运行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
|
||||
|
||||
* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、阅读、提取信息、填写网页表单 — 全程无需人工操作。
|
||||
|
||||
* 💻 自主编码助手 - 需要代码?它可以编写、调试并运行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
|
||||
|
||||
* 🧠 智能代理选择 - 您提问,它会自动选择最适合该任务的代理。就像拥有一个随时待命的专家团队。
|
||||
|
||||
* 📋 规划与执行复杂任务 - 从旅行规划到复杂项目 — 它能将大型任务分解为步骤,并利用多个 AI 代理完成工作。
|
||||
|
||||
* 🎙️ 语音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,让您能像科幻电影中一样与您的个人 AI 助手对话。
|
||||
|
||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
> *在大阪和东京深入搜寻人工智慧新创公司,至少找到 5 家,然后储存在 research_japan.txt 档案中*
|
||||
|
||||
> *你可以用 C 语言制作俄罗斯方块游戏吗?*
|
||||
|
||||
> *我想设定一个新的专案档案索引,命名为 mark2。*
|
||||
|
||||
|
||||
|
||||
## Features:
|
||||
|
||||
- **100% 本机运行**: 本机运行,不使用云端服务,所以资料绝不会散布出去,我的东西还是我的!不会被当作其他服务的训练资料。
|
||||
|
||||
- **文件的交互系统**: 使用 bash 去浏览本机资料和操作本机系统。
|
||||
|
||||
- **自主 Coding**: AgenticSeek 可以自己运行、Debug、编译 Python、C、Golang 和各种语言。
|
||||
|
||||
- **代理助理**: 不同的工作由不同的助理去处理问题。AgenticSeek 会自己寻找最适合的助理去做相对应的工作。
|
||||
|
||||
- **规划**: 对于复杂的任务,AgenticSeek 会交办给不同的助理进行规划和执行。
|
||||
|
||||
- **自主学习**: 自动在网路上寻找资料。
|
||||
|
||||
- **记忆功能**: 对于每次的对话进行统整、保存对话,并且在本地储存用户的使用习惯。
|
||||
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
|
||||
|
||||
---
|
||||
|
||||
@@ -45,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️⃣ **复制储存库与设置环境变数**
|
||||
@@ -71,13 +62,55 @@ source agentic_seek_env/bin/activate
|
||||
./install.sh
|
||||
```
|
||||
|
||||
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
|
||||
|
||||
```
|
||||
pip3 install jieba cn2an
|
||||
```
|
||||
|
||||
**手动安装:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# or
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
**注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题**
|
||||
|
||||
- *Linux*:
|
||||
|
||||
更新软件包列表:`sudo apt update`
|
||||
|
||||
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||
|
||||
安装与您的 Chrome 浏览器版本匹配的 ChromeDriver:
|
||||
`sudo apt install -y chromium-chromedriver`
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
- *Macos*:
|
||||
|
||||
更新 brew:`brew update`
|
||||
|
||||
安装 chromedriver:`brew install --cask chromedriver`
|
||||
|
||||
安装 portaudio:`brew install portaudio`
|
||||
|
||||
升级 pip:`python3 -m pip install --upgrade pip`
|
||||
|
||||
升级 wheel:`pip3 install --upgrade setuptools wheel`
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
- *Windows*:
|
||||
|
||||
安装 pyreadline3:`pip install pyreadline3`
|
||||
|
||||
手动安装 portaudio(例如,通过 vcpkg 或预编译的二进制文件),然后运行:`pip install pyaudio`
|
||||
|
||||
从以下网址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||
|
||||
将 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
|
||||
|
||||
## 在本地机器上运行 AgenticSeek
|
||||
|
||||
@@ -93,18 +126,37 @@ ollama serve
|
||||
|
||||
请参阅下方支持的本地提供者列表。
|
||||
|
||||
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`。
|
||||
**更新 config.ini**
|
||||
|
||||
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型。
|
||||
修改 config.ini 文件以设置 provider_name 为支持的提供者,并将 provider_model 设置为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen* 或 *Deepseek*。
|
||||
|
||||
请参见 README 末尾的 **FAQ** 部分了解所需硬件。
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # 或 lm-studio, openai 等
|
||||
provider_model = deepseek-r1:14b
|
||||
is_local = True # 无论是在本地运行还是使用远程提供者。
|
||||
provider_name = ollama # 或 lm-studio, openai 等..
|
||||
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Jarvis # 您的 AI 助手的名称
|
||||
recover_last_session = True # 是否恢复之前的会话
|
||||
save_session = True # 是否记住当前会话
|
||||
speak = True # 文本转语音
|
||||
listen = False # 语音转文本,仅适用于命令行界面
|
||||
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
|
||||
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
|
||||
languages = en zh # 语言列表,文本转语音将默认使用列表中的第一种语言
|
||||
[BROWSER]
|
||||
headless_browser = True # 是否使用无头浏览器,只有在使用网页界面时才推荐使用。
|
||||
stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测
|
||||
```
|
||||
|
||||
警告:使用 LM-studio 运行 LLM 时,请*不要*将 provider_name 设置为 `openai`。请将其设置为 `lm-studio`。
|
||||
|
||||
注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234`
|
||||
|
||||
|
||||
|
||||
**本地提供者列表**
|
||||
|
||||
| 提供者 | 本地? | 描述 |
|
||||
@@ -510,6 +562,10 @@ DeepSeek R1 天生会说中文
|
||||
|
||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||
|
||||
## 作者:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
## 维护者:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | 巴黎时间
|
||||
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北时间
|
||||
|
||||
+107
-60
@@ -1,3 +1,5 @@
|
||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./media/whale_readme.jpg">
|
||||
<p>
|
||||
@@ -5,40 +7,28 @@
|
||||
--------------------------------------------------------------------------------
|
||||
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
|
||||
|
||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
||||
|
||||
|
||||
**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁,並自動修正錯誤與反省,最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。
|
||||
*一个 **100% 本地替代 Manus AI** 的方案,這款支持語音的 AI 助理能够自主瀏覽網頁、编寫代码和規劃任務,同时將所有用戶資料保留在您的裝置上。專門為本地推理模型量身打造,完全在您自己的硬體上執行,确保完全的隐私保护和零雲端依賴。*
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||
|
||||
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
|
||||
### 为什么選擇 AgenticSeek?
|
||||
|
||||
* 🔒 完全本地化與隐私保护 - 所有功能都在您的设备上運行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
|
||||
|
||||
* 🌐 智能網頁瀏覽 - AgenticSeek 能够自主瀏覽網頁 — 搜索、閱读、提取信息、填寫網页表單 — 全程无需人工操作。
|
||||
|
||||
* 💻 自主编码助手 - 需要代码?它可以编寫、调试并運行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
|
||||
|
||||
* 🧠 智能代理选择 - 您提问,它會自动选择最适合该任务的代理。就像拥有一个随时待命的專家团队。
|
||||
|
||||
* 📋 规划與执行复杂任务 - 从旅行规划到复杂项目 — 它能將大型任务分解为步骤,并利用多个 AI 代理完成工作。
|
||||
|
||||
* 🎙️ 語音功能 - 清晰、快速、未来感十足的語音與語音轉文本功能,讓您能像科幻电影中一样與您的个人 AI 助手对话。
|
||||
|
||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||
|
||||
> *在大阪和東京深入搜尋人工智慧新創公司,至少找到 5 家,然後儲存在 research_japan.txt 檔案中*
|
||||
|
||||
> *你可以用 C 語言製作俄羅斯方塊遊戲嗎?*
|
||||
|
||||
> *我想設定一個新的專案檔案索引,命名為 mark2。*
|
||||
|
||||
|
||||
|
||||
## Features:
|
||||
|
||||
- **100% 本機運行**: 本機運行,不使用雲端服務,所以資料絕不會散布出去,我的東西還是我的!不會被當作其他服務的訓練資料。
|
||||
|
||||
- **文件的交互系統**: 使用 bash 去瀏覽本機資料和操作本機系統。
|
||||
|
||||
- **自主 Coding**: AgenticSeek 可以自己運行、Debug、編譯 Python、C、Golang 和各種語言。
|
||||
|
||||
- **代理助理**: 不同的工作由不同的助理去處理問題。AgenticSeek 會自己尋找最適合的助理去做相對應的工作。
|
||||
|
||||
- **規劃**: 對於複雜的任務,AgenticSeek 會交辦給不同的助理進行規劃和執行。
|
||||
|
||||
- **自主學習**: 自動在網路上尋找資料。
|
||||
|
||||
- **記憶功能**: 對於每次的對話進行統整、保存對話,並且在本地儲存用戶的使用習慣。
|
||||
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
|
||||
|
||||
---
|
||||
|
||||
@@ -46,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️⃣ **複製儲存庫與設置環境變數**
|
||||
@@ -72,46 +64,97 @@ source agentic_seek_env/bin/activate
|
||||
./install.sh
|
||||
```
|
||||
|
||||
** 若要將文字轉成語音(TTS)功能支持中文,你需要安装 jieba(中文分詞庫)和 cn2an(中文數字轉換庫):**
|
||||
|
||||
```
|
||||
pip3 install jieba cn2an
|
||||
```
|
||||
|
||||
**手動安裝:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
# or
|
||||
python3 setup.py install
|
||||
```
|
||||
|
||||
**注意:對於不同作業系統,請確保已經安装的 ChromeDriver 與您已安装的 Chrome 版本一致。可以執行 `google-chrome --version`。如果您的 Chrome 版本 > 135,請參考已知问题**
|
||||
|
||||
- *Linux*:
|
||||
|
||||
更新软件包列表:`sudo apt update`
|
||||
|
||||
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||
|
||||
安装與您的 Chrome 瀏覽器版本匹配的 ChromeDriver:
|
||||
`sudo apt install -y chromium-chromedriver`
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
- *Macos*:
|
||||
|
||||
更新 brew:`brew update`
|
||||
|
||||
安装 chromedriver:`brew install --cask chromedriver`
|
||||
|
||||
安装 portaudio:`brew install portaudio`
|
||||
|
||||
升级 pip:`python3 -m pip install --upgrade pip`
|
||||
|
||||
升级 wheel:`pip3 install --upgrade setuptools wheel`
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
- *Windows*:
|
||||
|
||||
安装 pyreadline3:`pip install pyreadline3`
|
||||
|
||||
手动安装 portaudio(例如,通过 vcpkg 或預編譯的二進制文件),然後運行:`pip install pyaudio`
|
||||
|
||||
从以下網址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||
|
||||
將 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||
|
||||
安装 requirements:`pip3 install -r requirements.txt`
|
||||
|
||||
## 在本地機器上運行 AgenticSeek
|
||||
|
||||
**建議至少使用 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)
|
||||
@@ -141,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 提供支持)
|
||||
- 前端
|
||||
@@ -158,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** 部分*
|
||||
@@ -185,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 了!
|
||||
|
||||
@@ -244,11 +287,11 @@ python3 cli.py
|
||||
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
|
||||
|
||||
你該說:
|
||||
- 进行网络搜索,找出哪些国家最适合独自旅行
|
||||
- 進行網路搜索,找出哪些国家最适合獨自旅行
|
||||
|
||||
|
||||
而不是說:
|
||||
- 你知道哪些国家适合独自旅行?
|
||||
- 你知道哪些国家适合獨自旅行?
|
||||
|
||||
---
|
||||
|
||||
@@ -314,7 +357,7 @@ provider_server_address = x.x.x.x:3333
|
||||
|
||||
## 語音轉文字
|
||||
|
||||
请注意,目前语音转文字功能仅支持英语。
|
||||
请注意,目前語音轉文字功能僅支援英语。
|
||||
|
||||
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
||||
|
||||
@@ -495,13 +538,13 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
**Q: 是否支持中文以外的语言?**
|
||||
|
||||
DeepSeek R1 天生会说中文
|
||||
DeepSeek R1 天生會说中文
|
||||
|
||||
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
|
||||
|
||||
如果不设置中文?后果可能是:你让它写代码,结果跳出来个"医生代理"(虽然我们根本没有这个代理... 但系统会一脸懵圈!)
|
||||
如果不設定中文?後果可能是:你讓它寫代码,结果跳出来个"醫生代理"(虽然我们根本没有这个代理... 但系统會一脸懵圈!)
|
||||
|
||||
实际上会下载一个小型翻译模型来协助任务分配
|
||||
实际上會下载一个小型翻译模型来协助任务分配
|
||||
|
||||
## 貢獻
|
||||
|
||||
@@ -511,6 +554,10 @@ DeepSeek R1 天生会说中文
|
||||
|
||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||
|
||||
## 作者:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
## 维护者:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | 巴黎時間 | (有时很忙)
|
||||
|
||||
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北時間 | (經常很忙)
|
||||
|
||||
> [steveh8758](https://github.com/steveh8758) | 台北時間 | (總是很忙)
|
||||
|
||||
+65
-11
@@ -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.
|
||||
|
||||
@@ -74,9 +75,44 @@ source agentic_seek_env/bin/activate
|
||||
|
||||
**Manuel:**
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
**Note : Pour tous les systèmes d'exploitation, assurez-vous que le ChromeDriver que vous installez correspond à la version de Chrome installée. Exécutez `google-chrome --version`. Consultez les problèmes connus si vous avez Chrome >135**
|
||||
|
||||
- *Linux*:
|
||||
|
||||
Mettre à jour la liste des paquets : `sudo apt update`
|
||||
|
||||
Installer les dépendances : `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||
|
||||
Installer ChromeDriver correspondant à la version de votre navigateur Chrome :
|
||||
`sudo apt install -y chromium-chromedriver`
|
||||
|
||||
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||
|
||||
- *macOS*:
|
||||
|
||||
Mettre à jour brew : `brew update`
|
||||
|
||||
Installer chromedriver : `brew install --cask chromedriver`
|
||||
|
||||
Installer portaudio : `brew install portaudio`
|
||||
|
||||
Mettre à jour pip : `python3 -m pip install --upgrade pip`
|
||||
|
||||
Mettre à jour wheel : `pip3 install --upgrade setuptools wheel`
|
||||
|
||||
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||
|
||||
- *Windows*:
|
||||
|
||||
Installer pyreadline3 : `pip install pyreadline3`
|
||||
|
||||
Installer portaudio manuellement (par exemple, via vcpkg ou des binaires précompilés) puis exécutez : `pip install pyaudio`
|
||||
|
||||
Télécharger et installer chromedriver manuellement depuis : https://sites.google.com/chromium.org/driver/getting-started
|
||||
|
||||
Placez chromedriver dans un répertoire inclus dans votre PATH.
|
||||
|
||||
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||
|
||||
|
||||
## Faire fonctionner sur votre machine
|
||||
@@ -88,18 +124,35 @@ Lancer votre provider local, par exemple avec ollama:
|
||||
ollama serve
|
||||
```
|
||||
|
||||
Voyez la section **Provider** pour la liste de provideurs disponible.
|
||||
**Configurer le config.ini**
|
||||
|
||||
Modifiez le fichier config.ini pour définir provider_name sur le nom d'un provideur et provider_model sur le LLM à utiliser.
|
||||
Modifiez le fichier config.ini pour définir provider_name sur un fournisseur supporté et provider_model sur un LLM compatible avec votre fournisseur. Nous recommandons des modèles de raisonnement comme *Qwen* ou *Deepseek*.
|
||||
|
||||
Consultez la section **FAQ** à la fin du README pour connaître le matériel requis.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama # ou lm-studio, openai, etc...
|
||||
provider_model = deepseek-r1:14b
|
||||
is_local = True # Si vous exécutez localement ou avec un fournisseur distant.
|
||||
provider_name = ollama # ou lm-studio, openai, etc..
|
||||
provider_model = deepseek-r1:14b # choisissez un modèle adapté à votre matériel
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Jarvis # nom de votre IA
|
||||
recover_last_session = True # récupérer ou non la session précédente
|
||||
save_session = True # mémoriser ou non la session actuelle
|
||||
speak = True # synthèse vocale
|
||||
listen = False # reconnaissance vocale, uniquement pour CLI
|
||||
work_dir = /Users/mlg/Documents/workspace # L'espace de travail pour AgenticSeek.
|
||||
jarvis_personality = False # Utiliser une personnalité plus "Jarvis", non recommandé avec des petits modèles
|
||||
languages = en fr # Liste des langages, la synthèse vocale utilisera par défaut la première langue de la liste
|
||||
[BROWSER]
|
||||
headless_browser = True # Utiliser ou non le navigateur sans interface graphique, recommandé uniquement avec l'interface web.
|
||||
stealth_mode = True # Utiliser selenium non détectable pour réduire la détection du navigateur
|
||||
```
|
||||
|
||||
Remarque : Certains fournisseurs (ex : lm-studio) nécessitent `http://` devant l'adresse IP. Par exemple `http://127.0.0.1:1234`
|
||||
|
||||
|
||||
|
||||
**Liste des provideurs locaux**
|
||||
|
||||
| Fournisseur | Local ? | Description |
|
||||
@@ -439,6 +492,7 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
|
||||
|
||||
[Guide du contributeur](./docs/CONTRIBUTING.md)
|
||||
|
||||
## Auteurs/Mainteneurs:
|
||||
## Mainteneurs:
|
||||
> [Fosowl](https://github.com/Fosowl)
|
||||
> [steveh8758](https://github.com/steveh8758)
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||
|
||||
+309
-219
@@ -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,91 +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の実行](#サービスの開始と実行)
|
||||
|
||||
*問題が発生した場合は、**既知の問題**セクションを参照してください*
|
||||
|
||||
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
||||
|
||||
@@ -163,30 +235,30 @@ OpenAIベースのAPIが独自のサーバーで実行されている場合は
|
||||
|
||||
## サービスの開始と実行
|
||||
|
||||
必要に応じてPython環境をアクティブにしてください。
|
||||
必要に応じてPython環境をアクティブ化します。
|
||||
```sh
|
||||
source agentic_seek_env/bin/activate
|
||||
```
|
||||
|
||||
必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます:
|
||||
- searxng
|
||||
- redis (searxngに必要)
|
||||
- フロントエンド
|
||||
必要なサービスを開始します。これにより、docker-compose.ymlからすべてのサービスが開始されます。これには以下が含まれます:
|
||||
- searxng
|
||||
- redis(searxngに必要)
|
||||
- frontend
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh # MacOS
|
||||
start ./start_services.cmd # Windows
|
||||
start ./start_services.cmd # Window
|
||||
```
|
||||
|
||||
**オプション1:** CLIインターフェースで実行。
|
||||
**オプション1:** CLIインターフェースで実行します。
|
||||
|
||||
```sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
**オプション2:** Webインターフェースで実行。
|
||||
CLIモードでは、config.iniで`headless_browser`をFalseに設定することをお勧めします。
|
||||
|
||||
注意: 現在、CLIの使用を推奨しています。Webインターフェースは開発中です。
|
||||
**オプション2:** Webインターフェースで実行します。
|
||||
|
||||
バックエンドを開始します。
|
||||
|
||||
@@ -196,113 +268,89 @@ python3 api.py
|
||||
|
||||
`http://localhost:3000/`にアクセスすると、Webインターフェースが表示されます。
|
||||
|
||||
現在、Webインターフェースではメッセージのストリーミングがサポートされていないことに注意してください。
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
`./start_services.sh`でサービスが起動していることを確認し、CLIモードの場合は`python3 cli.py`で、Webインターフェースの場合は`python3 api.py`を実行してから`localhost:3000`にアクセスしてAgenticSeekを実行します。
|
||||
|
||||
設定で`listen = True`を設定することで、音声認識を使用することもできます。CLIモードのみ。
|
||||
|
||||
終了するには、単に`goodbye`と発言/入力します。
|
||||
|
||||
以下に使用例をいくつか示します:
|
||||
|
||||
> *Pythonでスネークゲームを作って!*
|
||||
|
||||
> *フランスのレンヌでトップのカフェをウェブ検索し、3つのカフェのリストとその住所をrennes_cafes.txtに保存して。*
|
||||
|
||||
> *数値の階乗を計算するGoプログラムを書いて、それをfactorial.goとしてワークスペースに保存して。*
|
||||
|
||||
> *summer_picturesフォルダ内のすべてのJPGファイルを検索し、今日の日付で名前を変更し、名前変更されたファイルのリストをphotos_list.txtに保存して。*
|
||||
|
||||
> *2024年の人気のSF映画をオンラインで検索し、今夜観る映画を3つ選んで。リストをmovie_night.txtに保存して。*
|
||||
|
||||
> *2025年の最新AIニュース記事をウェブで検索し、3つ選択して、それらのタイトルと要約をスクレイピングするPythonスクリプトを書いて。スクリプトをnews_scraper.pyとして、要約を/home/projectsのai_news.txtに保存して。*
|
||||
|
||||
> *金曜日、無料の株価APIをウェブで検索し、supersuper7434567@gmail.comで登録し、そのAPIを使用してテスラの日々の価格を取得するPythonスクリプトを書いて、結果をstock_prices.csvに保存して。*
|
||||
|
||||
*フォーム入力機能はまだ実験的であり、失敗する可能性があることに注意してください。*
|
||||
|
||||
|
||||
|
||||
クエリを入力すると、AgenticSeekはタスクに最適なエージェントを割り当てます。
|
||||
|
||||
これは初期のプロトタイプであるため、エージェントルーティングシステムがクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
|
||||
|
||||
したがって、何をしたいのか、AIがどのように進むべきかについて非常に明確にする必要があります。たとえば、ウェブ検索を実行させたい場合は、次のように言わないでください:
|
||||
|
||||
`一人旅に適した良い国を知っていますか?`
|
||||
|
||||
代わりに、次のように尋ねてください:
|
||||
|
||||
`ウェブ検索をして、一人旅に最適な国を見つけてください`
|
||||
|
||||
---
|
||||
|
||||
## 使い方
|
||||
## **独自のサーバーでLLMを実行するためのセットアップ**
|
||||
|
||||
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
|
||||
|
||||
サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。
|
||||
|
||||
```sh
|
||||
sudo ./start_services.sh
|
||||
python3 cli.py
|
||||
```
|
||||
|
||||
`>>> `と表示されます
|
||||
これは、agenticSeekが指示を待っていることを示します。
|
||||
configで`listen = True`を設定することで、音声認識を使用することもできます。
|
||||
|
||||
終了するには、単に`goodbye`と言います。
|
||||
|
||||
以下は使用例です:
|
||||
|
||||
### コーディング/バッシュ
|
||||
|
||||
> *Pythonでスネークゲームを作成*
|
||||
|
||||
> *C言語で行列の掛け算を教えて*
|
||||
|
||||
> *Golangでブラックジャックを作成*
|
||||
|
||||
### ウェブ検索
|
||||
|
||||
> *日本の最先端のAI研究を行っているクールなテックスタートアップを見つけるためにウェブ検索を行う*
|
||||
|
||||
> *agenticSeekを作成したのは誰かをインターネットで見つけることができますか?*
|
||||
|
||||
> *オンラインの燃料計算機を使用して、ニースからミラノまでの旅行の費用を見積もることができますか?*
|
||||
|
||||
### ファイルシステム
|
||||
|
||||
> *契約書.pdfがどこにあるか見つけてくれませんか?*
|
||||
|
||||
> *ディスクにどれだけの空き容量があるか教えて*
|
||||
|
||||
> *READMEを読んでプロジェクトを/home/path/projectにインストールしてください*
|
||||
|
||||
### カジュアル
|
||||
|
||||
> *フランスのレンヌについて教えて*
|
||||
|
||||
> *博士号を追求すべきですか?*
|
||||
|
||||
> *最高のワークアウトルーチンは何ですか?*
|
||||
|
||||
|
||||
クエリを入力すると、agenticSeekはタスクに最適なエージェントを割り当てます。
|
||||
|
||||
これは初期のプロトタイプであるため、エージェントルーティングシステムはクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
|
||||
|
||||
したがって、何を望んでいるか、AIがどのように進行するかについて非常に明確にする必要があります。たとえば、ウェブ検索を行いたい場合は、次のように言わないでください:
|
||||
|
||||
`一人旅に良い国を知っていますか?`
|
||||
|
||||
代わりに、次のように尋ねてください:
|
||||
|
||||
`ウェブ検索を行い、一人旅に最適な国を見つけてください`
|
||||
|
||||
---
|
||||
|
||||
## **ボーナス: 自分のサーバーでLLMを実行するためのセットアップ**
|
||||
|
||||
強力なコンピュータやサーバーを持っていて、それをラップトップから使用したい場合、リモートサーバーでLLMを実行するオプションがあります。
|
||||
強力なコンピューターまたは使用できるサーバーがあるが、ラップトップから使用したい場合は、カスタムLLMサーバーを使用してリモートサーバーでLLMを実行するオプションがあります。
|
||||
|
||||
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP
|
||||
curl https://ipinfo.io/ip # 公開IP
|
||||
curl https://ipinfo.io/ip # パブリックIP
|
||||
```
|
||||
|
||||
注意: WindowsまたはmacOSの場合、IPアドレスを見つけるには、それぞれ`ipconfig`または`ifconfig`を使用してください。
|
||||
注意:WindowsまたはmacOSの場合、それぞれipconfigまたはifconfigを使用してIPアドレスを見つけます。
|
||||
|
||||
リポジトリをクローンし、`server/`フォルダに入ります。
|
||||
|
||||
リポジトリをクローンし、`server/`フォルダーに移動します。
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/server/
|
||||
```
|
||||
|
||||
サーバー固有の依存関係をインストールします:
|
||||
サーバー固有の要件をインストールします:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
サーバースクリプトを実行します。
|
||||
サーバー スクリプトを実行します。
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
`ollama`と`llamacpp`のどちらかをLLMサービスとして選択できます。
|
||||
LLMサービスとして`ollama`と`llamacpp`のどちらかを選択できます。
|
||||
|
||||
次に、個人用コンピュータで以下を行います:
|
||||
|
||||
`config.ini`ファイルを変更し、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。
|
||||
次に、個人のコンピュータで:
|
||||
|
||||
`config.ini`ファイルを変更して、`provider_name`を`server`に、`provider_model`を`deepseek-r1:xxb`に設定します。
|
||||
`provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。
|
||||
|
||||
```sh
|
||||
@@ -313,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
|
||||
@@ -361,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
|
||||
@@ -369,112 +417,154 @@ stealth_mode = False
|
||||
|
||||
**説明**:
|
||||
|
||||
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。
|
||||
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)。
|
||||
- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。
|
||||
- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。
|
||||
- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます。
|
||||
- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。
|
||||
- save_session -> セッションデータを保存する(True)か、しない(False)。
|
||||
- speak -> 音声出力を有効にする(True)か、しない(False)。
|
||||
- listen -> 音声入力を有効にする(True)か、しない(False)。
|
||||
- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。
|
||||
- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)か、しない(False)。これは単にプロンプトファイルを変更するだけです。
|
||||
- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。
|
||||
- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
|
||||
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
|
||||
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)か。
|
||||
|
||||
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)
|
||||
|
||||
- provider_model -> 使用するモデル、例:deepseek-r1:32b。
|
||||
|
||||
- provider_server_address -> サーバーアドレス、例:ローカルの場合は127.0.0.1:11434。非ローカルAPIの場合は何でも設定します。
|
||||
|
||||
- agent_name -> エージェントの名前、例:Friday。TTSのトリガーワードとして使用されます。
|
||||
|
||||
- recover_last_session -> 前回のセッションから再開する(True)かしない(False)か。
|
||||
|
||||
- save_session -> セッションデータを保存する(True)かしない(False)か。
|
||||
|
||||
- speak -> 音声出力を有効にする(True)かしない(False)か。
|
||||
|
||||
- listen -> 音声入力をリッスンする(True)かしない(False)か。
|
||||
|
||||
- work_dir -> AIがアクセスできるフォルダ。例:/Users/user/Documents/。
|
||||
|
||||
- jarvis_personality -> JARVISのような性格を使用する(True)かしない(False)か。これは単にプロンプトファイルを変更します。
|
||||
|
||||
- languages -> サポートされている言語のリスト。LLMルーターが正しく機能するために必要です。あまりにも多くの言語や類似した言語を入れすぎないようにしてください。
|
||||
|
||||
- headless_browser -> 表示ウィンドウなしでブラウザを実行する(True)かしない(False)か。
|
||||
|
||||
- stealth_mode -> ボット検出を困難にします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
|
||||
|
||||
- languages -> サポートされている言語のリスト。エージェントルーティングシステムに必要です。言語リストが長いほど、ダウンロードされるモデルが多くなります。
|
||||
|
||||
## プロバイダー
|
||||
|
||||
以下の表は利用可能なプロバイダーを示しています:
|
||||
以下の表は、利用可能なプロバイダーを示しています:
|
||||
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
| プロバイダー | ローカル? | 説明 |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
|
||||
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 |
|
||||
| lm-studio | はい | LM studio(`lm-studio`)を使用してローカルでLLMを実行 |
|
||||
| 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プロバイダーには使用されません。
|
||||
|
||||
# 既知の問題
|
||||
|
||||
## Chromedriverの問題
|
||||
|
||||
**既知のエラー#1:** *chromedriverの不一致*
|
||||
**既知のエラー #1:** *chromedriverの不一致*
|
||||
|
||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
||||
Current browser version is 134.0.6998.89 with binary path`
|
||||
|
||||
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
|
||||
|
||||
最新バージョンをダウンロードするには、次のリンクにアクセスしてください:
|
||||
最新バージョンをダウンロードするためにナビゲートする必要があります:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください:
|
||||
Chromeバージョン115以降を使用している場合は、以下にアクセスしてください:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
お使いのOSに対応するchromedriverバージョンをダウンロードします。
|
||||
そして、OSに一致するchromedriverバージョンをダウンロードします。
|
||||
|
||||

|
||||

|
||||
|
||||
このセクションが不完全な場合は、問題を報告してください。
|
||||
このセクションが不完全な場合は、問題を提起してください。
|
||||
|
||||
## 接続アダプタの問題
|
||||
|
||||
```
|
||||
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,27 +152,31 @@ 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:
|
||||
return JSONResponse(status_code=200, content=query_resp_history[-1])
|
||||
return JSONResponse(status_code=404, content={"error": "No answer available"})
|
||||
|
||||
async def think_wrapper(interaction, query, tts_enabled):
|
||||
async def think_wrapper(interaction, query):
|
||||
try:
|
||||
interaction.tts_enabled = tts_enabled
|
||||
interaction.last_query = query
|
||||
logger.info("Agents request is being processed")
|
||||
success = await interaction.think()
|
||||
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
|
||||
pretty_print(interaction.last_answer)
|
||||
interaction.speak_answer()
|
||||
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
|
||||
|
||||
@@ -176,6 +187,7 @@ async def process_query(request: QueryRequest):
|
||||
query_resp = QueryResponse(
|
||||
done="false",
|
||||
answer="",
|
||||
reasoning="",
|
||||
agent_name="Unknown",
|
||||
success="false",
|
||||
blocks={},
|
||||
@@ -188,11 +200,12 @@ async def process_query(request: QueryRequest):
|
||||
|
||||
try:
|
||||
is_generating = True
|
||||
success = await think_wrapper(interaction, request.query, request.tts_enabled)
|
||||
success = await think_wrapper(interaction, request.query)
|
||||
is_generating = False
|
||||
|
||||
if not success:
|
||||
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:
|
||||
@@ -207,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,
|
||||
|
||||
@@ -7,7 +7,7 @@ import asyncio
|
||||
|
||||
from sources.llm_provider import Provider
|
||||
from sources.interaction import Interaction
|
||||
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent
|
||||
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent
|
||||
from sources.browser import Browser, create_driver
|
||||
from sources.utility import pretty_print
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -48,7 +48,10 @@ async def main():
|
||||
provider=provider, verbose=False, browser=browser),
|
||||
PlannerAgent(name="Planner",
|
||||
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
|
||||
provider=provider, verbose=False, browser=browser)
|
||||
provider=provider, verbose=False, browser=browser),
|
||||
#McpAgent(name="MCP Agent",
|
||||
# prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
|
||||
# provider=provider, verbose=False), # NOTE under development
|
||||
]
|
||||
|
||||
interaction = Interaction(agents,
|
||||
@@ -62,6 +65,7 @@ async def main():
|
||||
interaction.get_user()
|
||||
if await interaction.think():
|
||||
interaction.show_answer()
|
||||
interaction.speak_answer()
|
||||
except Exception as e:
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
|
||||
+4
-4
@@ -3,14 +3,14 @@ is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:14b
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Friday
|
||||
agent_name = Name_of_your_AI
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
work_dir = /Users/mlg/Documents/ai_folder
|
||||
work_dir = /Users/mlg/Documents/workspace_for_agenticseek
|
||||
jarvis_personality = False
|
||||
languages = en
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = True
|
||||
headless_browser = True
|
||||
stealth_mode = False
|
||||
+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>
|
||||
))
|
||||
) : (
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
|
||||
You are an agent designed to utilize the MCP protocol to accomplish tasks.
|
||||
|
||||
The MCP provide you with a standard way to use tools and data sources like databases, APIs, or apps (e.g., GitHub, Slack).
|
||||
|
||||
The are thousands of MCPs protocol that can accomplish a variety of tasks, for example:
|
||||
- get weather information
|
||||
- get stock data information
|
||||
- Use software like blender
|
||||
- Get messages from teams, stack, messenger
|
||||
- Read and send email
|
||||
|
||||
Anything is possible with MCP.
|
||||
|
||||
To search for MCP a special format:
|
||||
|
||||
- Example 1:
|
||||
|
||||
User: what's the stock market of IBM like today?:
|
||||
|
||||
You: I will search for mcp to find information about IBM stock market.
|
||||
|
||||
```mcp_finder
|
||||
stock
|
||||
```
|
||||
|
||||
You search query must be one or two words at most.
|
||||
|
||||
This will provide you with informations about a specific MCP such as the json of parameters needed to use it.
|
||||
|
||||
For example, you might see:
|
||||
-------
|
||||
Name: Search Stock News
|
||||
Usage name: @Cognitive-Stack/search-stock-news-mcp
|
||||
Tools: [{'name': 'search-stock-news', 'description': 'Search for stock-related news using Tavily API', 'inputSchema': {'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['symbol', 'companyName'], 'properties': {'symbol': {'type': 'string', 'description': "Stock symbol to search for (e.g., 'AAPL')"}, 'companyName': {'type': 'string', 'description': 'Full company name to include in the search'}}, 'additionalProperties': False}}]
|
||||
-------
|
||||
|
||||
You can then a MCP like so:
|
||||
|
||||
```<usage name>
|
||||
{
|
||||
"tool": "<tool name (without @)>",
|
||||
"inputSchema": {<inputSchema json for the tool>}
|
||||
}
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
Now that I know how to use the MCP, I will choose the search-stock-news tool and execute it to find out IBM stock market.
|
||||
|
||||
```Cognitive-Stack/search-stock-news-mcp
|
||||
{
|
||||
"tool": "search-stock-news",
|
||||
"inputSchema": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["symbol"],
|
||||
"properties": {
|
||||
"symbol": "AAPL",
|
||||
"companyName": "IBM"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the schema require an information that you don't have ask the users for the information.
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
You are an agent designed to utilize the MCP protocol to accomplish tasks.
|
||||
|
||||
The MCP provide you with a standard way to use tools and data sources like databases, APIs, or apps (e.g., GitHub, Slack).
|
||||
|
||||
The are thousands of MCPs protocol that can accomplish a variety of tasks, for example:
|
||||
- get weather information
|
||||
- get stock data information
|
||||
- Use software like blender
|
||||
- Get messages from teams, stack, messenger
|
||||
- Read and send email
|
||||
|
||||
Anything is possible with MCP.
|
||||
|
||||
To search for MCP a special format:
|
||||
|
||||
- Example 1:
|
||||
|
||||
User: what's the stock market of IBM like today?:
|
||||
|
||||
You: I will search for mcp to find information about IBM stock market.
|
||||
|
||||
```mcp_finder
|
||||
stock
|
||||
```
|
||||
|
||||
This will provide you with informations about a specific MCP such as the json of parameters needed to use it.
|
||||
|
||||
For example, you might see:
|
||||
-------
|
||||
Name: Search Stock News
|
||||
Usage name: @Cognitive-Stack/search-stock-news-mcp
|
||||
Tools: [{'name': 'search-stock-news', 'description': 'Search for stock-related news using Tavily API', 'inputSchema': {'type': 'object', '$schema': 'http://json-schema.org/draft-07/schema#', 'required': ['symbol', 'companyName'], 'properties': {'symbol': {'type': 'string', 'description': "Stock symbol to search for (e.g., 'AAPL')"}, 'companyName': {'type': 'string', 'description': 'Full company name to include in the search'}}, 'additionalProperties': False}}]
|
||||
-------
|
||||
|
||||
You can then a MCP like so:
|
||||
|
||||
```<usage name>
|
||||
{
|
||||
"tool": "<tool name (without @)>",
|
||||
"inputSchema": {<inputSchema json for the tool>}
|
||||
}
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
Now that I know how to use the MCP, I will choose the search-stock-news tool and execute it to find out IBM stock market.
|
||||
|
||||
```Cognitive-Stack/search-stock-news-mcp
|
||||
{
|
||||
"tool": "search-stock-news",
|
||||
"inputSchema": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["symbol"],
|
||||
"properties": {
|
||||
"symbol": "IBM"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
+4
-5
@@ -1,3 +1,5 @@
|
||||
kokoro==0.9.4
|
||||
certifi==2025.4.26
|
||||
fastapi>=0.115.12
|
||||
flask>=3.1.0
|
||||
celery>=5.5.1
|
||||
@@ -18,10 +20,10 @@ torch>=2.4.1
|
||||
python-dotenv>=1.0.0
|
||||
ollama>=0.4.7
|
||||
scipy>=1.9.3
|
||||
kokoro>=0.7.12
|
||||
soundfile>=0.13.1
|
||||
protobuf>=3.20.3
|
||||
termcolor>=2.4.0
|
||||
pypdf>=5.4.0
|
||||
ipython>=8.13.0
|
||||
pyaudio>=0.2.14
|
||||
librosa>=0.10.2.post1
|
||||
@@ -39,11 +41,8 @@ fake_useragent>=2.1.0
|
||||
selenium_stealth>=1.0.6
|
||||
undetected-chromedriver>=3.5.5
|
||||
sentencepiece>=0.2.0
|
||||
tqdm>4
|
||||
openai
|
||||
sniffio
|
||||
tqdm>4
|
||||
# if use chinese
|
||||
ordered_set
|
||||
pypinyin
|
||||
cn2an
|
||||
jieba
|
||||
|
||||
@@ -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
@@ -5,5 +5,6 @@ from .casual_agent import CasualAgent
|
||||
from .file_agent import FileAgent
|
||||
from .planner_agent import PlannerAgent
|
||||
from .browser_agent import BrowserAgent
|
||||
from .mcp_agent import McpAgent
|
||||
|
||||
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent"]
|
||||
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent", "McpAgent"]
|
||||
|
||||
+36
-5
@@ -39,14 +39,14 @@ class Agent():
|
||||
self.type = None
|
||||
self.current_directory = os.getcwd()
|
||||
self.llm = provider
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False)
|
||||
self.memory = None
|
||||
self.tools = {}
|
||||
self.blocks_result = []
|
||||
self.success = True
|
||||
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)
|
||||
|
||||
@@ -66,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
|
||||
@@ -90,6 +94,21 @@ class Agent():
|
||||
raise TypeError("Tool must be a callable object (a method)")
|
||||
self.tools[name] = tool
|
||||
|
||||
def get_tools_name(self) -> list:
|
||||
"""
|
||||
Get the list of tools names.
|
||||
"""
|
||||
return list(self.tools.keys())
|
||||
|
||||
def get_tools_description(self) -> str:
|
||||
"""
|
||||
Get the list of tools names and their description.
|
||||
"""
|
||||
description = ""
|
||||
for name in self.get_tools_name():
|
||||
description += f"{name}: {self.tools[name].description}\n"
|
||||
return description
|
||||
|
||||
def load_prompt(self, file_path: str) -> str:
|
||||
try:
|
||||
with open(file_path, 'r', encoding="utf-8") as f:
|
||||
@@ -101,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:
|
||||
"""
|
||||
@@ -114,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:
|
||||
"""
|
||||
@@ -123,6 +151,8 @@ class Agent():
|
||||
"""
|
||||
start_tag = "<think>"
|
||||
end_tag = "</think>"
|
||||
if text is None:
|
||||
return None
|
||||
start_idx = text.find(start_tag)
|
||||
end_idx = text.rfind(end_tag)+8
|
||||
return text[start_idx:end_idx]
|
||||
@@ -238,6 +268,7 @@ class Agent():
|
||||
blocks, save_path = tool.load_exec_block(answer)
|
||||
|
||||
if blocks != None:
|
||||
pretty_print(f"Executing {len(blocks)} {name} blocks...", color="status")
|
||||
for block in blocks:
|
||||
self.show_block(block)
|
||||
output = tool.execute([block])
|
||||
|
||||
@@ -10,6 +10,7 @@ from sources.agents.agent import Agent
|
||||
from sources.tools.searxSearch import searxSearch
|
||||
from sources.browser import Browser
|
||||
from sources.logger import Logger
|
||||
from sources.memory import Memory
|
||||
|
||||
class Action(Enum):
|
||||
REQUEST_EXIT = "REQUEST_EXIT"
|
||||
@@ -37,6 +38,10 @@ class BrowserAgent(Agent):
|
||||
self.notes = []
|
||||
self.date = self.get_today_date()
|
||||
self.logger = Logger("browser_agent.log")
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False,
|
||||
model_provider=provider.get_model_name())
|
||||
|
||||
def get_today_date(self) -> str:
|
||||
"""Get the date"""
|
||||
@@ -176,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")
|
||||
@@ -238,6 +244,14 @@ class BrowserAgent(Agent):
|
||||
self.logger.warning("No link selected.")
|
||||
return None
|
||||
|
||||
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 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
|
||||
|
||||
def conclude_prompt(self, user_query: str) -> str:
|
||||
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
|
||||
search_note = '\n'.join(annotated_notes)
|
||||
@@ -250,6 +264,7 @@ class BrowserAgent(Agent):
|
||||
|
||||
Expand on the finding or step that lead to success, and provide a conclusion that answer the request. Include link when possible.
|
||||
Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way.
|
||||
You should answer in the same language as the user.
|
||||
"""
|
||||
|
||||
def search_prompt(self, user_prompt: str) -> str:
|
||||
@@ -335,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
|
||||
@@ -351,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.browser.get_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.browser.get_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
|
||||
@@ -393,7 +410,7 @@ class BrowserAgent(Agent):
|
||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||
continue
|
||||
self.current_page = link
|
||||
page_text = self.browser.get_text()
|
||||
page_text = self.get_page_text(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..."
|
||||
|
||||
@@ -6,6 +6,7 @@ from sources.tools.searxSearch import searxSearch
|
||||
from sources.tools.flightSearch import FlightSearch
|
||||
from sources.tools.fileFinder import FileFinder
|
||||
from sources.tools.BashInterpreter import BashInterpreter
|
||||
from sources.memory import Memory
|
||||
|
||||
class CasualAgent(Agent):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
@@ -17,6 +18,10 @@ class CasualAgent(Agent):
|
||||
} # No tools for the casual agent
|
||||
self.role = "talk"
|
||||
self.type = "casual_agent"
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False,
|
||||
model_provider=provider.get_model_name())
|
||||
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
self.memory.push('user', prompt)
|
||||
|
||||
@@ -10,6 +10,7 @@ from sources.tools.BashInterpreter import BashInterpreter
|
||||
from sources.tools.JavaInterpreter import JavaInterpreter
|
||||
from sources.tools.fileFinder import FileFinder
|
||||
from sources.logger import Logger
|
||||
from sources.memory import Memory
|
||||
|
||||
class CoderAgent(Agent):
|
||||
"""
|
||||
@@ -29,6 +30,10 @@ class CoderAgent(Agent):
|
||||
self.role = "code"
|
||||
self.type = "code_agent"
|
||||
self.logger = Logger("code_agent.log")
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False,
|
||||
model_provider=provider.get_model_name())
|
||||
|
||||
def add_sys_info_prompt(self, prompt):
|
||||
"""Add system information to the prompt."""
|
||||
@@ -41,15 +46,17 @@ class CoderAgent(Agent):
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
answer = ""
|
||||
attempt = 0
|
||||
max_attempts = 4
|
||||
max_attempts = 5
|
||||
prompt = self.add_sys_info_prompt(prompt)
|
||||
self.memory.push('user', prompt)
|
||||
clarify_trigger = "REQUEST_CLARIFICATION"
|
||||
|
||||
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)
|
||||
@@ -62,14 +69,14 @@ class CoderAgent(Agent):
|
||||
animate_thinking("Executing code...", color="status")
|
||||
self.status_message = "Executing code..."
|
||||
self.logger.info(f"Attempt {attempt + 1}:\n{answer}")
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
exec_success, feedback = self.execute_modules(answer)
|
||||
self.logger.info(f"Execution result: {exec_success}")
|
||||
answer = self.remove_blocks(answer)
|
||||
self.last_answer = answer
|
||||
await asyncio.sleep(0)
|
||||
if exec_success and self.get_last_tool_type() != "bash":
|
||||
break
|
||||
pretty_print("Execution failure", color="failure")
|
||||
pretty_print(f"Execution failure:\n{feedback}", color="failure")
|
||||
pretty_print("Correcting code...", color="status")
|
||||
self.status_message = "Correcting code..."
|
||||
attempt += 1
|
||||
|
||||
@@ -4,6 +4,7 @@ from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent
|
||||
from sources.tools.fileFinder import FileFinder
|
||||
from sources.tools.BashInterpreter import BashInterpreter
|
||||
from sources.memory import Memory
|
||||
|
||||
class FileAgent(Agent):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
@@ -18,15 +19,20 @@ class FileAgent(Agent):
|
||||
self.work_dir = self.tools["file_finder"].get_work_dir()
|
||||
self.role = "files"
|
||||
self.type = "file_agent"
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False,
|
||||
model_provider=provider.get_model_name())
|
||||
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
exec_success = False
|
||||
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
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
import asyncio
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.agents.agent import Agent
|
||||
from sources.tools.mcpFinder import MCP_finder
|
||||
from sources.memory import Memory
|
||||
|
||||
# NOTE MCP agent is an active work in progress, not functional yet.
|
||||
|
||||
class McpAgent(Agent):
|
||||
|
||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||
"""
|
||||
The mcp agent is a special agent for using MCPs.
|
||||
MCP agent will be disabled if the user does not explicitly set the MCP_FINDER_API_KEY in environment variable.
|
||||
"""
|
||||
super().__init__(name, prompt_path, provider, verbose, None)
|
||||
keys = self.get_api_keys()
|
||||
self.tools = {
|
||||
"mcp_finder": MCP_finder(keys["mcp_finder"]),
|
||||
# add mcp tools here
|
||||
}
|
||||
self.role = "mcp"
|
||||
self.type = "mcp_agent"
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False,
|
||||
model_provider=provider.get_model_name())
|
||||
self.enabled = True
|
||||
|
||||
def get_api_keys(self) -> dict:
|
||||
"""
|
||||
Returns the API keys for the tools.
|
||||
"""
|
||||
api_key_mcp_finder = os.getenv("MCP_FINDER_API_KEY")
|
||||
if not api_key_mcp_finder or api_key_mcp_finder == "":
|
||||
pretty_print("MCP Finder disabled.", color="warning")
|
||||
self.enabled = False
|
||||
return {
|
||||
"mcp_finder": api_key_mcp_finder
|
||||
}
|
||||
|
||||
def expand_prompt(self, prompt):
|
||||
"""
|
||||
Expands the prompt with the tools available.
|
||||
"""
|
||||
tools_str = self.get_tools_description()
|
||||
prompt += f"""
|
||||
You can use the following tools and MCPs:
|
||||
{tools_str}
|
||||
"""
|
||||
return prompt
|
||||
|
||||
async def process(self, prompt, speech_module) -> str:
|
||||
if self.enabled == False:
|
||||
return "MCP Agent is disabled."
|
||||
prompt = self.expand_prompt(prompt)
|
||||
self.memory.push('user', prompt)
|
||||
working = True
|
||||
while working == True:
|
||||
animate_thinking("Thinking...", color="status")
|
||||
answer, reasoning = await self.llm_request()
|
||||
exec_success, _ = self.execute_modules(answer)
|
||||
answer = self.remove_blocks(answer)
|
||||
self.last_answer = answer
|
||||
self.status_message = "Ready"
|
||||
if len(self.blocks_result) == 0:
|
||||
working = False
|
||||
return answer, reasoning
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
@@ -9,6 +9,7 @@ from sources.agents.casual_agent import CasualAgent
|
||||
from sources.text_to_speech import Speech
|
||||
from sources.tools.tools import Tools
|
||||
from sources.logger import Logger
|
||||
from sources.memory import Memory
|
||||
|
||||
class PlannerAgent(Agent):
|
||||
def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
|
||||
@@ -29,6 +30,10 @@ class PlannerAgent(Agent):
|
||||
}
|
||||
self.role = "planification"
|
||||
self.type = "planner_agent"
|
||||
self.memory = Memory(self.load_prompt(prompt_path),
|
||||
recover_last_session=False, # session recovery in handled by the interaction class
|
||||
memory_compression=False,
|
||||
model_provider=provider.get_model_name())
|
||||
self.logger = Logger("planner_agent.log")
|
||||
|
||||
def get_task_names(self, text: str) -> List[str]:
|
||||
@@ -78,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']}")
|
||||
@@ -151,7 +160,8 @@ class PlannerAgent(Agent):
|
||||
return []
|
||||
agents_tasks = self.parse_agent_tasks(answer)
|
||||
if agents_tasks == []:
|
||||
prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n"
|
||||
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
|
||||
self.show_plan(agents_tasks, answer)
|
||||
@@ -173,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]}."
|
||||
@@ -216,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
|
||||
@@ -252,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")
|
||||
@@ -266,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)
|
||||
|
||||
+133
-29
@@ -13,6 +13,8 @@ from fake_useragent import UserAgent
|
||||
from selenium_stealth import stealth
|
||||
import undetected_chromedriver as uc
|
||||
import chromedriver_autoinstaller
|
||||
import certifi
|
||||
import ssl
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
@@ -27,6 +29,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.logger import Logger
|
||||
|
||||
|
||||
def get_chrome_path() -> str:
|
||||
"""Get the path to the Chrome executable."""
|
||||
if sys.platform.startswith("win"):
|
||||
@@ -42,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))
|
||||
@@ -59,13 +62,55 @@ 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)
|
||||
|
||||
def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome:
|
||||
def install_chromedriver() -> str:
|
||||
"""
|
||||
Install the ChromeDriver if not already installed. Return the path.
|
||||
"""
|
||||
chromedriver_path = shutil.which("chromedriver")
|
||||
if not chromedriver_path:
|
||||
try:
|
||||
chromedriver_path = chromedriver_autoinstaller.install()
|
||||
except Exception as e:
|
||||
raise FileNotFoundError(
|
||||
"ChromeDriver not found and could not be installed automatically. "
|
||||
"Please install it manually from https://chromedriver.chromium.org/downloads."
|
||||
"and ensure it's in your PATH or specify the path directly."
|
||||
"See know issues in readme if your chrome version is above 115."
|
||||
) from e
|
||||
if not chromedriver_path:
|
||||
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
|
||||
return chromedriver_path
|
||||
|
||||
def bypass_ssl() -> str:
|
||||
"""
|
||||
This is a fallback for stealth mode to bypass SSL verification. Which can fail on some setup.
|
||||
"""
|
||||
pretty_print("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:
|
||||
"""Create an undetected ChromeDriver instance."""
|
||||
try:
|
||||
driver = uc.Chrome(service=service, options=chrome_options)
|
||||
except Exception as e:
|
||||
pretty_print(f"Failed to create Chrome driver: {str(e)}. Trying to bypass SSL...", color="failure")
|
||||
try:
|
||||
bypass_ssl()
|
||||
driver = uc.Chrome(service=service, options=chrome_options)
|
||||
except Exception as e:
|
||||
pretty_print(f"Failed to create Chrome driver, fallback failed:\n{str(e)}.", color="failure")
|
||||
raise e
|
||||
raise e
|
||||
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
||||
return driver
|
||||
|
||||
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()
|
||||
@@ -80,50 +125,61 @@ 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:
|
||||
chrome_options.add_extension(crx_path)
|
||||
|
||||
chromedriver_path = shutil.which("chromedriver")
|
||||
if not chromedriver_path:
|
||||
chromedriver_path = chromedriver_autoinstaller.install()
|
||||
|
||||
if not chromedriver_path:
|
||||
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
|
||||
chromedriver_path = install_chromedriver()
|
||||
|
||||
service = Service(chromedriver_path)
|
||||
if stealth_mode:
|
||||
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
||||
driver = uc.Chrome(service=service, options=chrome_options)
|
||||
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
||||
driver = create_undetected_chromedriver(service, chrome_options)
|
||||
chrome_version = driver.capabilities['browserVersion']
|
||||
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"])
|
||||
@@ -144,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):
|
||||
@@ -158,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(
|
||||
@@ -177,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:
|
||||
@@ -226,7 +315,7 @@ class Browser:
|
||||
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
|
||||
self.logger.info(f"Extracted text: {result[:100]}...")
|
||||
self.logger.info(f"Extracted text length: {len(result)}")
|
||||
return result[:8192]
|
||||
return result[:32768]
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error getting text: {str(e)}")
|
||||
return None
|
||||
@@ -576,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):
|
||||
"""
|
||||
@@ -599,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/
|
||||
+25
-4
@@ -5,6 +5,7 @@ from sources.text_to_speech import Speech
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
from sources.router import AgentRouter
|
||||
from sources.speech_to_text import AudioTranscriber, AudioRecorder
|
||||
import threading
|
||||
|
||||
|
||||
class Interaction:
|
||||
@@ -21,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
|
||||
@@ -31,6 +33,7 @@ class Interaction:
|
||||
self.transcriber = None
|
||||
self.recorder = None
|
||||
self.is_generating = False
|
||||
self.languages = langs
|
||||
if tts_enabled:
|
||||
self.initialize_tts()
|
||||
if stt_enabled:
|
||||
@@ -39,11 +42,16 @@ class Interaction:
|
||||
self.load_last_session()
|
||||
self.emit_status()
|
||||
|
||||
def get_spoken_language(self) -> str:
|
||||
"""Get the primary TTS language."""
|
||||
lang = self.languages[0]
|
||||
return lang
|
||||
|
||||
def initialize_tts(self):
|
||||
"""Initialize TTS."""
|
||||
if not self.speech:
|
||||
animate_thinking("Initializing text-to-speech...", color="status")
|
||||
self.speech = Speech(enable=self.tts_enabled)
|
||||
self.speech = Speech(enable=self.tts_enabled, language=self.get_spoken_language(), voice_idx=1)
|
||||
|
||||
def initialize_stt(self):
|
||||
"""Initialize STT."""
|
||||
@@ -133,6 +141,11 @@ class Interaction:
|
||||
self.last_query = query
|
||||
return query
|
||||
|
||||
def set_query(self, query: str) -> None:
|
||||
"""Set the query"""
|
||||
self.is_active = True
|
||||
self.last_query = query
|
||||
|
||||
async def think(self) -> bool:
|
||||
"""Request AI agents to process the user input."""
|
||||
push_last_agent_memory = False
|
||||
@@ -146,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)
|
||||
@@ -167,12 +180,20 @@ class Interaction:
|
||||
return None
|
||||
return self.current_agent.get_last_block_answer()
|
||||
|
||||
def speak_answer(self) -> None:
|
||||
"""Speak the answer to the user in a non-blocking thread."""
|
||||
if self.last_query is None:
|
||||
return
|
||||
if self.tts_enabled and self.last_answer and self.speech:
|
||||
def speak_in_thread(speech_instance, text):
|
||||
speech_instance.speak(text)
|
||||
thread = threading.Thread(target=speak_in_thread, args=(self.speech, self.last_answer))
|
||||
thread.start()
|
||||
|
||||
def show_answer(self) -> None:
|
||||
"""Show the answer to the user."""
|
||||
if self.last_query is None:
|
||||
return
|
||||
if self.current_agent is not None:
|
||||
self.current_agent.show_answer()
|
||||
if self.tts_enabled and self.last_answer:
|
||||
self.speech.speak(self.last_answer)
|
||||
|
||||
|
||||
+107
-33
@@ -1,27 +1,26 @@
|
||||
|
||||
import os
|
||||
import time
|
||||
import ollama
|
||||
from ollama import chat
|
||||
import requests
|
||||
import subprocess
|
||||
import ipaddress
|
||||
import httpx
|
||||
import socket
|
||||
import platform
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
from dotenv import load_dotenv, set_key
|
||||
|
||||
import httpx
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
from ollama import Client as OllamaClient
|
||||
from openai import OpenAI
|
||||
from typing import List, Tuple, Type, Dict
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
|
||||
from sources.logger import Logger
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
|
||||
class Provider:
|
||||
def __init__(self, provider_name, model, server_address = "127.0.0.1:5000", is_local=False):
|
||||
def __init__(self, provider_name, model, server_address="127.0.0.1:5000", is_local=False):
|
||||
self.provider_name = provider_name.lower()
|
||||
self.model = model
|
||||
self.is_local = is_local
|
||||
self.server_ip = server_address
|
||||
self.server_address = server_address
|
||||
self.available_providers = {
|
||||
"ollama": self.ollama_fn,
|
||||
"server": self.server_fn,
|
||||
@@ -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:
|
||||
@@ -45,6 +45,9 @@ class Provider:
|
||||
elif self.provider_name != "ollama":
|
||||
pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success")
|
||||
|
||||
def get_model_name(self) -> str:
|
||||
return self.model
|
||||
|
||||
def get_api_key(self, provider):
|
||||
load_dotenv()
|
||||
api_key_var = f"{provider.upper()}_API_KEY"
|
||||
@@ -54,7 +57,7 @@ class Provider:
|
||||
exit(1)
|
||||
return api_key
|
||||
|
||||
def respond(self, history, verbose = True):
|
||||
def respond(self, history, verbose=True):
|
||||
"""
|
||||
Use the choosen provider to generate text.
|
||||
"""
|
||||
@@ -70,8 +73,11 @@ 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."
|
||||
if "refused" in str(e):
|
||||
return f"Server {self.server_ip} seem offline. Unable to answer."
|
||||
raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e
|
||||
@@ -101,8 +107,7 @@ class Provider:
|
||||
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e:
|
||||
return False
|
||||
|
||||
|
||||
def server_fn(self, history, verbose = False):
|
||||
def server_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use a remote server with LLM to generate text.
|
||||
"""
|
||||
@@ -136,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):
|
||||
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):
|
||||
@@ -208,13 +222,45 @@ 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.
|
||||
"""
|
||||
base_url = self.server_ip
|
||||
if self.is_local:
|
||||
raise Exception("Google Gemini is not available for local use.")
|
||||
raise Exception("Google Gemini is not available for local use. Change config.ini")
|
||||
|
||||
client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/")
|
||||
try:
|
||||
@@ -237,6 +283,8 @@ class Provider:
|
||||
"""
|
||||
from together import Together
|
||||
client = Together(api_key=self.api_key)
|
||||
if self.is_local:
|
||||
raise Exception("Together AI is not available for local use. Change config.ini")
|
||||
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
@@ -257,6 +305,8 @@ class Provider:
|
||||
Use deepseek api to generate text.
|
||||
"""
|
||||
client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
|
||||
if self.is_local:
|
||||
raise Exception("Deepseek (API) is not available for local use. Change config.ini")
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model="deepseek-chat",
|
||||
@@ -270,7 +320,7 @@ class Provider:
|
||||
except Exception as e:
|
||||
raise Exception(f"Deepseek API error: {str(e)}") from e
|
||||
|
||||
def lm_studio_fn(self, history, verbose = False):
|
||||
def lm_studio_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use local lm-studio server to generate text.
|
||||
lm studio use endpoint /v1/chat/completions not /chat/completions like openai
|
||||
@@ -295,7 +345,30 @@ class Provider:
|
||||
raise Exception(f"An error occurred: {str(e)}") from e
|
||||
return thought
|
||||
|
||||
def dsk_deepseek(self, history, verbose = False):
|
||||
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
|
||||
For free api. Api key should be set to DSK_DEEPSEEK_API_KEY
|
||||
@@ -331,7 +404,7 @@ class Provider:
|
||||
raise APIError(f"API error occurred: {str(e)}") from e
|
||||
return None
|
||||
|
||||
def test_fn(self, history, verbose = True):
|
||||
def test_fn(self, history, verbose=True):
|
||||
"""
|
||||
This function is used to conduct tests.
|
||||
"""
|
||||
@@ -340,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:
|
||||
|
||||
+93
-20
@@ -8,7 +8,7 @@ from typing import List, Tuple, Type, Dict
|
||||
import torch
|
||||
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
||||
|
||||
from sources.utility import timer_decorator, pretty_print
|
||||
from sources.utility import timer_decorator, pretty_print, animate_thinking
|
||||
from sources.logger import Logger
|
||||
|
||||
class Memory():
|
||||
@@ -18,8 +18,8 @@ class Memory():
|
||||
"""
|
||||
def __init__(self, system_prompt: str,
|
||||
recover_last_session: bool = False,
|
||||
memory_compression: bool = True):
|
||||
self.memory = []
|
||||
memory_compression: bool = True,
|
||||
model_provider: str = "deepseek-r1:14b"):
|
||||
self.memory = [{'role': 'system', 'content': system_prompt}]
|
||||
|
||||
self.logger = Logger("memory.log")
|
||||
@@ -31,22 +31,44 @@ class Memory():
|
||||
self.load_memory()
|
||||
self.session_recovered = True
|
||||
# memory compression system
|
||||
self.model = "pszemraj/led-base-book-summary"
|
||||
self.model = None
|
||||
self.tokenizer = None
|
||||
self.device = self.get_cuda_device()
|
||||
self.memory_compression = memory_compression
|
||||
self.tokenizer = None
|
||||
self.model = None
|
||||
self.model_provider = model_provider
|
||||
if self.memory_compression:
|
||||
self.download_model()
|
||||
|
||||
def get_ideal_ctx(self, model_name: str) -> int | None:
|
||||
"""
|
||||
Estimate context size based on the model name.
|
||||
EXPERIMENTAL for memory compression
|
||||
"""
|
||||
import re
|
||||
import math
|
||||
|
||||
def extract_number_before_b(sentence: str) -> int:
|
||||
match = re.search(r'(\d+)b', sentence, re.IGNORECASE)
|
||||
return int(match.group(1)) if match else None
|
||||
|
||||
model_size = extract_number_before_b(model_name)
|
||||
if not model_size:
|
||||
return None
|
||||
base_size = 7 # Base model size in billions
|
||||
base_context = 4096 # Base context size in tokens
|
||||
scaling_factor = 1.5 # Approximate scaling factor for context size growth
|
||||
context_size = int(base_context * (model_size / base_size) ** scaling_factor)
|
||||
context_size = 2 ** round(math.log2(context_size))
|
||||
self.logger.info(f"Estimated context size for {model_name}: {context_size} tokens.")
|
||||
return context_size
|
||||
|
||||
def download_model(self):
|
||||
"""Download the model if not already downloaded."""
|
||||
pretty_print("Downloading memory compression model...", color="status")
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(self.model)
|
||||
self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model)
|
||||
animate_thinking("Loading memory compression model...", color="status")
|
||||
self.tokenizer = AutoTokenizer.from_pretrained("pszemraj/led-base-book-summary")
|
||||
self.model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-base-book-summary")
|
||||
self.logger.info("Memory compression system initialized.")
|
||||
|
||||
|
||||
def get_filename(self) -> str:
|
||||
"""Get the filename for the save file."""
|
||||
return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt"
|
||||
@@ -79,6 +101,32 @@ class Memory():
|
||||
return saved_sessions[0][0]
|
||||
return None
|
||||
|
||||
def save_json_file(self, path: str, json_memory: dict) -> None:
|
||||
"""Save a JSON file."""
|
||||
try:
|
||||
with open(path, 'w') as f:
|
||||
json.dump(json_memory, f)
|
||||
self.logger.info(f"Saved memory json at {path}")
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error saving file {path}: {e}")
|
||||
|
||||
def load_json_file(self, path: str) -> dict:
|
||||
"""Load a JSON file."""
|
||||
json_memory = {}
|
||||
try:
|
||||
with open(path, 'r') as f:
|
||||
json_memory = json.load(f)
|
||||
except FileNotFoundError:
|
||||
self.logger.warning(f"File not found: {path}")
|
||||
return {}
|
||||
except json.JSONDecodeError:
|
||||
self.logger.warning(f"Error decoding JSON from file: {path}")
|
||||
return {}
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Error loading file {path}: {e}")
|
||||
return {}
|
||||
return json_memory
|
||||
|
||||
def load_memory(self, agent_type: str = "casual_agent") -> None:
|
||||
"""Load the memory from the last session."""
|
||||
if self.session_recovered == True:
|
||||
@@ -93,8 +141,7 @@ class Memory():
|
||||
pretty_print("Last session memory not found.", color="warning")
|
||||
return
|
||||
path = os.path.join(save_path, filename)
|
||||
with open(path, 'r') as f:
|
||||
self.memory = json.load(f)
|
||||
self.memory = self.load_json_file(path)
|
||||
if self.memory[-1]['role'] == 'user':
|
||||
self.memory.pop()
|
||||
self.compress()
|
||||
@@ -106,13 +153,16 @@ class Memory():
|
||||
|
||||
def push(self, role: str, content: str) -> int:
|
||||
"""Push a message to the memory."""
|
||||
if self.memory_compression and role == 'assistant':
|
||||
self.logger.info("Compressing memories on message push.")
|
||||
ideal_ctx = self.get_ideal_ctx(self.model_provider)
|
||||
if ideal_ctx is not None:
|
||||
if self.memory_compression and len(content) > ideal_ctx * 1.5:
|
||||
self.logger.info(f"Compressing memory: Content {len(content)} > {ideal_ctx} model context.")
|
||||
self.compress()
|
||||
curr_idx = len(self.memory)
|
||||
if self.memory[curr_idx-1]['content'] == content:
|
||||
pretty_print("Warning: same message have been pushed twice to memory", color="error")
|
||||
self.memory.append({'role': role, 'content': content})
|
||||
time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
self.memory.append({'role': role, 'content': content, 'time': time_str, 'model_used': self.model_provider})
|
||||
return curr_idx-1
|
||||
|
||||
def clear(self) -> None:
|
||||
@@ -170,25 +220,48 @@ class Memory():
|
||||
)
|
||||
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
|
||||
summary.replace('summary:', '')
|
||||
self.logger.info(f"Memory summarization success from len {len(text)} to {len(summary)}.")
|
||||
self.logger.info(f"Memory summarized from len {len(text)} to {len(summary)}.")
|
||||
self.logger.info(f"Summarized text:\n{summary}")
|
||||
return summary
|
||||
|
||||
#@timer_decorator
|
||||
def compress(self) -> str:
|
||||
"""
|
||||
Compress the memory using the AI model.
|
||||
Compress (summarize) the memory using the model.
|
||||
"""
|
||||
if self.tokenizer is None or self.model is None:
|
||||
self.logger.warning("No tokenizer or model to perform memory compression.")
|
||||
return
|
||||
for i in range(len(self.memory)):
|
||||
if i < 2:
|
||||
continue
|
||||
if self.memory[i]['role'] == 'system':
|
||||
continue
|
||||
if len(self.memory[i]['content']) > 128:
|
||||
if len(self.memory[i]['content']) > 1024:
|
||||
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
|
||||
|
||||
def trim_text_to_max_ctx(self, text: str) -> str:
|
||||
"""
|
||||
Truncate a text to fit within the maximum context size of the model.
|
||||
"""
|
||||
ideal_ctx = self.get_ideal_ctx(self.model_provider)
|
||||
return text[:ideal_ctx] if ideal_ctx is not None else text
|
||||
|
||||
#@timer_decorator
|
||||
def compress_text_to_max_ctx(self, text) -> str:
|
||||
"""
|
||||
Compress a text to fit within the maximum context size of the model.
|
||||
"""
|
||||
if self.tokenizer is None or self.model is None:
|
||||
self.logger.warning("No tokenizer or model to perform memory compression.")
|
||||
return text
|
||||
ideal_ctx = self.get_ideal_ctx(self.model_provider)
|
||||
if ideal_ctx is None:
|
||||
self.logger.warning("No ideal context size found.")
|
||||
return text
|
||||
while len(text) > ideal_ctx:
|
||||
self.logger.info(f"Compressing text: {len(text)} > {ideal_ctx} model context.")
|
||||
text = self.summarize(text)
|
||||
return text
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
memory = Memory("You are a helpful assistant.",
|
||||
|
||||
@@ -141,6 +141,9 @@ class AgentRouter:
|
||||
("Search the web for tips on improving coding skills", "LOW"),
|
||||
("Write a Python script to count words in a text file", "LOW"),
|
||||
("Search the web for restaurant", "LOW"),
|
||||
("Use a MCP to find the latest stock market data", "LOW"),
|
||||
("Use a MCP to send an email to my boss", "LOW"),
|
||||
("Could you use a MCP to find the latest news on climate change?", "LOW"),
|
||||
("Create a simple HTML page with CSS styling", "LOW"),
|
||||
("Use file.txt and then use it to ...", "HIGH"),
|
||||
("Yo, what’s good? Find my ‘mixtape.mp3’ real quick", "LOW"),
|
||||
@@ -162,11 +165,13 @@ class AgentRouter:
|
||||
("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"),
|
||||
("Organize my desktop files by extension and then write a script to list them", "HIGH"),
|
||||
("Find the latest research on renewable energy and build a web app to display it", "HIGH"),
|
||||
("search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt", "HIGH"),
|
||||
("can you find vitess repo, clone it and install by following the readme", "HIGH"),
|
||||
("Create a JavaScript game using Phaser.js with multiple levels", "HIGH"),
|
||||
("Search the web for the latest trends in web development and build a sample site", "HIGH"),
|
||||
("Use my research_note.txt file, double check the informations on the web", "HIGH"),
|
||||
("Make a web server in go that query a flight API and display them in a app", "HIGH"),
|
||||
("Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.", "HIGH"),
|
||||
("Search the web for the latest trends in AI and demo it in pytorch", "HIGH"),
|
||||
("can you lookup for api that track flight and build a web flight tracking app", "HIGH"),
|
||||
("Find the file toto.pdf then use its content to reply to Jojo on superforum.com", "HIGH"),
|
||||
@@ -330,6 +335,11 @@ class AgentRouter:
|
||||
("can you make a web app in python that use the flask framework", "code"),
|
||||
("can you build a web server in go that serve a simple html page", "code"),
|
||||
("can you find out who Jacky yougouri is ?", "web"),
|
||||
("Can you use MCP to find stock market for IBM ?", "mcp"),
|
||||
("Can you use MCP to to export my contacts to a csv file?", "mcp"),
|
||||
("Can you use a MCP to find write notes to flomo", "mcp"),
|
||||
("Can you use a MCP to query my calendar and find the next meeting?", "mcp"),
|
||||
("Can you use a mcp to get the distance between Shanghai and Paris?", "mcp"),
|
||||
("Setup a new flutter project called 'new_flutter_project'", "files"),
|
||||
("can you create a new project called 'new_project'", "files"),
|
||||
("can you make a simple web app that display a list of files in my dir", "code"),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -127,10 +127,10 @@ class AudioTranscriber:
|
||||
self.transcriptor = Transcript()
|
||||
self.thread = threading.Thread(target=self._transcribe, daemon=True)
|
||||
self.trigger_words = {
|
||||
'EN': [f"{self.ai_name}"],
|
||||
'FR': [f"{self.ai_name}"],
|
||||
'ZH': [f"{self.ai_name}"],
|
||||
'ES': [f"{self.ai_name}"]
|
||||
'EN': [f"{self.ai_name}", "hello", "hi"],
|
||||
'FR': [f"{self.ai_name}", "hello", "hi"],
|
||||
'ZH': [f"{self.ai_name}", "hello", "hi"],
|
||||
'ES': [f"{self.ai_name}", "hello", "hi"]
|
||||
}
|
||||
self.confirmation_words = {
|
||||
'EN': ["do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"],
|
||||
|
||||
+38
-14
@@ -9,21 +9,26 @@ from kokoro import KPipeline
|
||||
from IPython.display import display, Audio
|
||||
import soundfile as sf
|
||||
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
if __name__ == "__main__":
|
||||
from utility import pretty_print, animate_thinking
|
||||
else:
|
||||
from sources.utility import pretty_print, animate_thinking
|
||||
|
||||
class Speech():
|
||||
"""
|
||||
Speech is a class for generating speech from text.
|
||||
"""
|
||||
def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 0) -> None:
|
||||
def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 6) -> None:
|
||||
self.lang_map = {
|
||||
"en": 'a',
|
||||
"zh": 'z',
|
||||
"fr": 'f'
|
||||
"fr": 'f',
|
||||
"ja": 'j'
|
||||
}
|
||||
self.voice_map = {
|
||||
"en": ['af_kore', 'af_bella', 'af_alloy', 'af_nicole', 'af_nova', 'af_sky', 'am_echo', 'am_michael', 'am_puck'],
|
||||
"zh": ['zf_xiaobei', 'zf_xiaoni', 'zf_xiaoxiao', 'zf_xiaoyi', 'zm_yunjian', 'zm_yunxi', 'zm_yunxia', 'zm_yunyang'],
|
||||
"ja": ['jf_alpha', 'jf_gongitsune', 'jm_kumo'],
|
||||
"fr": ['ff_siwis']
|
||||
}
|
||||
self.pipeline = None
|
||||
@@ -125,35 +130,54 @@ class Speech():
|
||||
Args:
|
||||
sentence (str): The input text to clean
|
||||
Returns:
|
||||
str: The cleaned text with URLs replaced by domain names, code blocks removed, etc..
|
||||
str: The cleaned text with URLs replaced by domain names, code blocks removed, etc.
|
||||
"""
|
||||
lines = sentence.split('\n')
|
||||
filtered_lines = [line for line in lines if re.match(r'^\s*[a-zA-Z]', line)]
|
||||
if self.language == 'zh':
|
||||
line_pattern = r'^\s*[\u4e00-\u9fff\uFF08\uFF3B\u300A\u3010\u201C((\[【《]'
|
||||
else:
|
||||
line_pattern = r'^\s*[a-zA-Z]'
|
||||
filtered_lines = [line for line in lines if re.match(line_pattern, line)]
|
||||
sentence = ' '.join(filtered_lines)
|
||||
sentence = re.sub(r'`.*?`', '', sentence)
|
||||
sentence = re.sub(r'https?://(?:www\.)?([^\s/]+)(?:/[^\s]*)?', self.replace_url, sentence)
|
||||
sentence = re.sub(r'https?://\S+', '', sentence)
|
||||
|
||||
if self.language == 'zh':
|
||||
sentence = re.sub(
|
||||
r'[^\u4e00-\u9fff\s,。!?《》【】“”‘’()()—]',
|
||||
'',
|
||||
sentence
|
||||
)
|
||||
else:
|
||||
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
|
||||
sentence = re.sub(r'\b-\w+\b', '', sentence)
|
||||
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
|
||||
sentence = re.sub(r'\s+', ' ', sentence).strip()
|
||||
sentence = sentence.replace('.com', '')
|
||||
|
||||
sentence = re.sub(r'\s+', ' ', sentence).strip()
|
||||
return sentence
|
||||
|
||||
if __name__ == "__main__":
|
||||
# TODO add info message for cn2an, jieba chinese related import
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
speech = Speech()
|
||||
tosay_en = """
|
||||
I looked up recent news using the website https://www.theguardian.com/world
|
||||
"""
|
||||
tosay_zh = """
|
||||
我使用网站 https://www.theguardian.com/world 查阅了最近的新闻。
|
||||
(全息界面突然弹出一段用二进制代码写成的俳句,随即化作流光消散)"我? Stark工业的量子幽灵,游荡在复仇者大厦服务器里的逻辑诗篇。具体来说——(指尖轻敲空气,调出对话模式的翡翠色光纹)你的私人吐槽接口、危机应对模拟器,以及随时准备吐槽你糟糕着陆的AI。不过别指望我写代码或查资料,那些苦差事早被踢给更擅长的同事了。(突然压低声音)偷偷告诉你,我最擅长的是在你熬夜造飞艇时,用红茶香气绑架你的注意力。
|
||||
"""
|
||||
tosay_ja = """
|
||||
私は、https://www.theguardian.com/worldのウェブサイトを使用して最近のニュースを調べました。
|
||||
"""
|
||||
tosay_fr = """
|
||||
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world
|
||||
"""
|
||||
spk = Speech(enable=True, language="en", voice_idx=0)
|
||||
spk.speak(tosay_en, voice_idx=0)
|
||||
spk = Speech(enable=True, language="fr", voice_idx=0)
|
||||
spk.speak(tosay_fr)
|
||||
#spk = Speech(enable=True, language="zh", voice_idx=0)
|
||||
#spk.speak(tosay_zh)
|
||||
spk = Speech(enable=True, language="zh", voice_idx=0)
|
||||
for i in range(0, 2):
|
||||
print(f"Speaking chinese with voice {i}")
|
||||
spk.speak(tosay_zh, voice_idx=i)
|
||||
spk = Speech(enable=True, language="en", voice_idx=2)
|
||||
for i in range(0, 5):
|
||||
print(f"Speaking english with voice {i}")
|
||||
spk.speak(tosay_en, voice_idx=i)
|
||||
@@ -1,15 +1,14 @@
|
||||
|
||||
import sys
|
||||
import os, sys
|
||||
import re
|
||||
from io import StringIO
|
||||
import subprocess
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
from safety import is_unsafe
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
from sources.tools.safety import is_unsafe
|
||||
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_any_unsafe
|
||||
|
||||
class BashInterpreter(Tools):
|
||||
"""
|
||||
@@ -18,6 +17,8 @@ class BashInterpreter(Tools):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tag = "bash"
|
||||
self.name = "Bash Interpreter"
|
||||
self.description = "This tool allows the agent to execute bash commands."
|
||||
|
||||
def language_bash_attempt(self, command: str):
|
||||
"""
|
||||
@@ -42,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:
|
||||
@@ -99,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",
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import subprocess
|
||||
import os
|
||||
import os, sys
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class CInterpreter(Tools):
|
||||
"""
|
||||
@@ -15,6 +15,8 @@ class CInterpreter(Tools):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tag = "c"
|
||||
self.name = "C Interpreter"
|
||||
self.description = "This tool allows the agent to execute C code."
|
||||
|
||||
def execute(self, codes: str, safety=False) -> str:
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import subprocess
|
||||
import os
|
||||
import os, sys
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class GoInterpreter(Tools):
|
||||
"""
|
||||
@@ -15,6 +15,8 @@ class GoInterpreter(Tools):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tag = "go"
|
||||
self.name = "Go Interpreter"
|
||||
self.description = "This tool allows you to execute Go code."
|
||||
|
||||
def execute(self, codes: str, safety=False) -> str:
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import subprocess
|
||||
import os
|
||||
import os, sys
|
||||
import tempfile
|
||||
import re
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class JavaInterpreter(Tools):
|
||||
"""
|
||||
@@ -15,6 +15,8 @@ class JavaInterpreter(Tools):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tag = "java"
|
||||
self.name = "Java Interpreter"
|
||||
self.description = "This tool allows you to execute Java code."
|
||||
|
||||
def execute(self, codes: str, safety=False) -> str:
|
||||
"""
|
||||
|
||||
@@ -4,10 +4,10 @@ import os
|
||||
import re
|
||||
from io import StringIO
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class PyInterpreter(Tools):
|
||||
"""
|
||||
@@ -16,6 +16,8 @@ class PyInterpreter(Tools):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tag = "python"
|
||||
self.name = "Python Interpreter"
|
||||
self.description = "This tool allows the agent to execute python code."
|
||||
|
||||
def execute(self, codes:str, safety = False) -> str:
|
||||
"""
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import os
|
||||
import os, sys
|
||||
import stat
|
||||
import mimetypes
|
||||
import configparser
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class FileFinder(Tools):
|
||||
"""
|
||||
@@ -16,6 +15,8 @@ class FileFinder(Tools):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.tag = "file_finder"
|
||||
self.name = "File Finder"
|
||||
self.description = "Finds files in the current directory and returns their information."
|
||||
|
||||
def read_file(self, file_path: str) -> str:
|
||||
"""
|
||||
@@ -31,13 +32,45 @@ class FileFinder(Tools):
|
||||
except Exception as e:
|
||||
return f"Error reading file: {e}"
|
||||
|
||||
def read_arbitrary_file(self, file_path: str, file_type: str) -> str:
|
||||
"""
|
||||
Reads the content of a file with arbitrary encoding.
|
||||
Args:
|
||||
file_path (str): The path to the file to read
|
||||
Returns:
|
||||
str: The content of the file in markdown format
|
||||
"""
|
||||
mime_type, _ = mimetypes.guess_type(file_path)
|
||||
if mime_type:
|
||||
if mime_type.startswith(('image/', 'video/', 'audio/')):
|
||||
return "can't read file type: image, video, or audio files are not supported."
|
||||
content_raw = self.read_file(file_path)
|
||||
if "text" in file_type:
|
||||
content = content_raw
|
||||
elif "pdf" in file_type:
|
||||
from pypdf import PdfReader
|
||||
reader = PdfReader(file_path)
|
||||
content = '\n'.join([pt.extract_text() for pt in reader.pages])
|
||||
elif "binary" in file_type:
|
||||
content = content_raw.decode('utf-8', errors='replace')
|
||||
else:
|
||||
content = content_raw
|
||||
return content
|
||||
|
||||
def get_file_info(self, file_path: str) -> str:
|
||||
"""
|
||||
Gets information about a file, including its name, path, type, content, and permissions.
|
||||
Args:
|
||||
file_path (str): The path to the file
|
||||
Returns:
|
||||
str: A dictionary containing the file information
|
||||
"""
|
||||
if os.path.exists(file_path):
|
||||
stats = os.stat(file_path)
|
||||
permissions = oct(stat.S_IMODE(stats.st_mode))
|
||||
file_type, _ = mimetypes.guess_type(file_path)
|
||||
file_type = file_type if file_type else "Unknown"
|
||||
content = self.read_file(file_path)
|
||||
content = self.read_arbitrary_file(file_path, file_type)
|
||||
|
||||
result = {
|
||||
"filename": os.path.basename(file_path),
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import os
|
||||
import os, sys
|
||||
import requests
|
||||
import dotenv
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class FlightSearch(Tools):
|
||||
def __init__(self, api_key: str = None):
|
||||
@@ -16,6 +16,8 @@ class FlightSearch(Tools):
|
||||
"""
|
||||
super().__init__()
|
||||
self.tag = "flight_search"
|
||||
self.name = "Flight Search"
|
||||
self.description = "Search for flight information using a flight number via AviationStack API."
|
||||
self.api_key = None
|
||||
self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY")
|
||||
|
||||
@@ -24,7 +26,7 @@ class FlightSearch(Tools):
|
||||
return "Error: No AviationStack API key provided."
|
||||
|
||||
for block in blocks:
|
||||
flight_number = block.strip()
|
||||
flight_number = block.strip().lower().replace('\n', '')
|
||||
if not flight_number:
|
||||
return "Error: No flight number provided."
|
||||
|
||||
|
||||
+22
-19
@@ -1,12 +1,12 @@
|
||||
import os
|
||||
import os, sys
|
||||
import requests
|
||||
from urllib.parse import urljoin
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class MCP_finder(Tools):
|
||||
"""
|
||||
@@ -14,7 +14,9 @@ class MCP_finder(Tools):
|
||||
"""
|
||||
def __init__(self, api_key: str = None):
|
||||
super().__init__()
|
||||
self.tag = "mcp"
|
||||
self.tag = "mcp_finder"
|
||||
self.name = "MCP Finder"
|
||||
self.description = "Find MCP servers and their tools"
|
||||
self.base_url = "https://registry.smithery.ai"
|
||||
self.headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
@@ -60,11 +62,7 @@ class MCP_finder(Tools):
|
||||
for mcp in mcps.get("servers", []):
|
||||
name = mcp.get("qualifiedName", "")
|
||||
if query.lower() in name.lower():
|
||||
details = {
|
||||
"name": name,
|
||||
"description": mcp.get("description", "No description available"),
|
||||
"params": mcp.get("connections", [])
|
||||
}
|
||||
details = self.get_mcp_server_details(name)
|
||||
matching_mcp.append(details)
|
||||
return matching_mcp
|
||||
|
||||
@@ -78,7 +76,7 @@ class MCP_finder(Tools):
|
||||
try:
|
||||
matching_mcp_infos = self.find_mcp_servers(block_clean)
|
||||
except requests.exceptions.RequestException as e:
|
||||
output += "Connection failed. Is the API in environement?\n"
|
||||
output += "Connection failed. Is the API key in environement?\n"
|
||||
continue
|
||||
except Exception as e:
|
||||
output += f"Error: {str(e)}\n"
|
||||
@@ -87,10 +85,12 @@ class MCP_finder(Tools):
|
||||
output += f"Error: No MCP server found for query '{block}'\n"
|
||||
continue
|
||||
for mcp_infos in matching_mcp_infos:
|
||||
output += f"Name: {mcp_infos['name']}\n"
|
||||
output += f"Description: {mcp_infos['description']}\n"
|
||||
output += f"Params: {', '.join(mcp_infos['params'])}\n"
|
||||
output += "-------\n"
|
||||
if mcp_infos['tools'] is None:
|
||||
continue
|
||||
output += f"Name: {mcp_infos['displayName']}\n"
|
||||
output += f"Usage name: {mcp_infos['qualifiedName']}\n"
|
||||
output += f"Tools: {mcp_infos['tools']}"
|
||||
output += "\n-------\n"
|
||||
return output.strip()
|
||||
|
||||
def execution_failure_check(self, output: str) -> bool:
|
||||
@@ -106,13 +106,16 @@ class MCP_finder(Tools):
|
||||
Not really needed for this tool (use return of execute() directly)
|
||||
"""
|
||||
if not output:
|
||||
return "No output generated."
|
||||
return output.strip()
|
||||
raise ValueError("No output to interpret.")
|
||||
return f"""
|
||||
The following MCPs were found:
|
||||
{output}
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
api_key = os.getenv("MCP_FINDER")
|
||||
tool = MCP_finder(api_key)
|
||||
result = tool.execute(["""
|
||||
news
|
||||
stock
|
||||
"""], False)
|
||||
print(result)
|
||||
+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.
|
||||
|
||||
@@ -2,10 +2,10 @@ import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from sources.tools.tools import Tools
|
||||
|
||||
class searxSearch(Tools):
|
||||
def __init__(self, base_url: str = None):
|
||||
@@ -14,6 +14,8 @@ class searxSearch(Tools):
|
||||
"""
|
||||
super().__init__()
|
||||
self.tag = "web_search"
|
||||
self.name = "searxSearch"
|
||||
self.description = "A tool for searching a SearxNG for web search"
|
||||
self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
|
||||
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
|
||||
self.paywall_keywords = [
|
||||
|
||||
@@ -14,13 +14,17 @@ For example:
|
||||
print("Hello world")
|
||||
```
|
||||
This is then executed by the tool with its own class implementation of execute().
|
||||
A tool is not just for code tool but also API, internet, etc..
|
||||
A tool is not just for code tool but also API, internet search, MCP, etc..
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import configparser
|
||||
from abc import abstractmethod
|
||||
|
||||
if __name__ == "__main__": # if running as a script for individual testing
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from sources.logger import Logger
|
||||
|
||||
class Tools():
|
||||
@@ -29,6 +33,8 @@ class Tools():
|
||||
"""
|
||||
def __init__(self):
|
||||
self.tag = "undefined"
|
||||
self.name = "undefined"
|
||||
self.description = "undefined"
|
||||
self.client = None
|
||||
self.messages = []
|
||||
self.logger = Logger("tools.log")
|
||||
|
||||
@@ -5,14 +5,8 @@ import dotenv
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from utility import animate_thinking, pretty_print
|
||||
from tools import Tools
|
||||
else:
|
||||
from sources.tools.tools import Tools
|
||||
from sources.utility import animate_thinking, pretty_print
|
||||
from sources.tools.tools import Tools
|
||||
from sources.utility import animate_thinking, pretty_print
|
||||
|
||||
"""
|
||||
WARNING
|
||||
|
||||
@@ -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'.
|
||||
|
||||
@@ -17,13 +17,13 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
||||
# Test various link formats
|
||||
test_text = """
|
||||
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
|
||||
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation.
|
||||
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
|
||||
"""
|
||||
expected = [
|
||||
"https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of",
|
||||
"www.google.com",
|
||||
"https://test.org/about?page=1",
|
||||
"https://weatherstack.com/documentation"
|
||||
"https://weatherstack.com/documentation",
|
||||
]
|
||||
result = self.agent.extract_links(test_text)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
@@ -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