Compare commits
141
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c81c0ffde6 | ||
|
|
444e7bce22 | ||
|
|
d3f20819ff | ||
|
|
9f0fdd547e | ||
|
|
be1bfc5cf2 | ||
|
|
a3b0bb22aa | ||
|
|
fc74d4361a | ||
|
|
54cc2a03ec | ||
|
|
b96e83dbbe | ||
|
|
eadcfb66d1 | ||
|
|
1c4a550c6f | ||
|
|
95aeaf74fa | ||
|
|
ec1f7d31fb | ||
|
|
abae98cf77 | ||
|
|
819a3fb98d | ||
|
|
7d74a348c9 | ||
|
|
a3ad635728 | ||
|
|
58656ab43c | ||
|
|
c1a1e9409d | ||
|
|
41fe95fcb1 | ||
|
|
c8bccc2395 | ||
|
|
e45fa86cda | ||
|
|
c8df9e759c | ||
|
|
b3672c60c0 | ||
|
|
97460ded48 | ||
|
|
9a34ff3646 | ||
|
|
a53842b8b7 | ||
|
|
0a8d898e0b | ||
|
|
92f721886c | ||
|
|
58f46d4351 | ||
|
|
ee6687df85 | ||
|
|
8d15546771 | ||
|
|
7ad084b27f | ||
|
|
63cd5eddd7 | ||
|
|
50f9e11a35 | ||
|
|
500605d5da | ||
|
|
6ec9647d19 | ||
|
|
16b8f1a451 | ||
|
|
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 | ||
|
|
ccef61b2b9 | ||
|
|
3cf1cab68f | ||
|
|
0579fd3bb6 | ||
|
|
c6688355a7 | ||
|
|
68ed1834a9 | ||
|
|
487670d207 | ||
|
|
a109ac98ed | ||
|
|
d928a95ed1 | ||
|
|
ffa6873a86 | ||
|
|
db2eb6fbac | ||
|
|
68d471bfc6 | ||
|
|
03c71368f5 | ||
|
|
2fd83289fd | ||
|
|
7dd60a8946 | ||
|
|
ccaf1fae52 | ||
|
|
34e9baccf3 |
@@ -0,0 +1,18 @@
|
|||||||
|
# Python cache files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
# Virtual environments
|
||||||
|
agentic_seek_env/
|
||||||
|
.agentic_seek_env/
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Git metadata
|
||||||
|
.git/
|
||||||
|
|
||||||
|
# macOS Finder files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
*.log
|
||||||
@@ -1,3 +1,12 @@
|
|||||||
SEARXNG_BASE_URL="http://127.0.0.1:8080"
|
SEARXNG_BASE_URL="http://127.0.0.1:8080"
|
||||||
|
REDIS_BASE_URL="redis://redis:6379/0"
|
||||||
|
WORK_DIR="/Users/username/Documents/workspace_with_my_files"
|
||||||
|
OLLAMA_PORT="11434"
|
||||||
|
LM_STUDIO_PORT="1234"
|
||||||
|
CUSTOM_ADDITIONAL_LLM_PORT="11435"
|
||||||
OPENAI_API_KEY='xxxxx'
|
OPENAI_API_KEY='xxxxx'
|
||||||
DEEPSEEK_API_KEY='xxxxx'
|
DEEPSEEK_API_KEY='xxxxx'
|
||||||
|
OPENROUTER_API_KEY='xxxxx'
|
||||||
|
TOGETHER_API_KEY='xxxxx'
|
||||||
|
GOOGLE_API_KEY='xxxxx'
|
||||||
|
ANTHROPIC_API_KEY='xxxxx'
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [Fosowl ]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ test_agent.py
|
|||||||
config.ini
|
config.ini
|
||||||
.voices/
|
.voices/
|
||||||
experimental/
|
experimental/
|
||||||
|
chrome_bundle/
|
||||||
.logs/
|
.logs/
|
||||||
.screenshots/*.png
|
.screenshots/*.png
|
||||||
.screenshots/*.jpg
|
.screenshots/*.jpg
|
||||||
@@ -18,6 +19,7 @@ agentic_seek_env/*
|
|||||||
.env
|
.env
|
||||||
*/.env
|
*/.env
|
||||||
dsk/
|
dsk/
|
||||||
|
chrome136/
|
||||||
|
|
||||||
### react ###
|
### react ###
|
||||||
.DS_*
|
.DS_*
|
||||||
|
|||||||
+65
-9
@@ -1,10 +1,31 @@
|
|||||||
FROM ubuntu:22.04
|
|
||||||
# Warning: doesn't work yet, backend is run on host machine for now
|
|
||||||
|
|
||||||
WORKDIR /app
|
FROM --platform=linux/amd64 python:3.11-slim
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
RUN apt-get update -qq -y && \
|
# Install essential packages and Chrome dependencies
|
||||||
apt-get install -y \
|
RUN apt-get update -y && apt-get install -y \
|
||||||
|
wget \
|
||||||
|
gnupg2 \
|
||||||
|
ca-certificates \
|
||||||
|
unzip \
|
||||||
|
xvfb \
|
||||||
|
libxss1 \
|
||||||
|
libappindicator1 \
|
||||||
|
fonts-liberation \
|
||||||
|
libnss3 \
|
||||||
|
libatk1.0-0 \
|
||||||
|
libatk-bridge2.0-0 \
|
||||||
|
libcups2 \
|
||||||
|
libdrm2 \
|
||||||
|
libxcomposite1 \
|
||||||
|
libxdamage1 \
|
||||||
|
libxrandr2 \
|
||||||
|
xdg-utils \
|
||||||
|
dbus \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN apt-get update -y && \
|
||||||
|
apt-get install -y \
|
||||||
gcc \
|
gcc \
|
||||||
g++ \
|
g++ \
|
||||||
gfortran \
|
gfortran \
|
||||||
@@ -24,23 +45,58 @@ apt-get install -y \
|
|||||||
libgtk-4-1 \
|
libgtk-4-1 \
|
||||||
libnss3 \
|
libnss3 \
|
||||||
xdg-utils \
|
xdg-utils \
|
||||||
wget && \
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
||||||
|
RUN apt-get update -y && \
|
||||||
|
apt-get install -y \
|
||||||
|
alsa-utils \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
ENV CHROME_TESTING_VERSION=134.0.6998.88
|
||||||
|
ENV DISPLAY=:99
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN set -eux; \
|
||||||
|
wget -qO /tmp/chrome.zip \
|
||||||
|
"https://storage.googleapis.com/chrome-for-testing-public/${CHROME_TESTING_VERSION}/linux64/chrome-linux64.zip"; \
|
||||||
|
unzip -q /tmp/chrome.zip -d /opt; \
|
||||||
|
rm /tmp/chrome.zip; \
|
||||||
|
ln -s /opt/chrome-linux64/chrome /usr/local/bin/google-chrome; \
|
||||||
|
ln -s /opt/chrome-linux64/chrome /usr/local/bin/chrome; \
|
||||||
|
mkdir -p /opt/chrome; \
|
||||||
|
ln -s /opt/chrome-linux64/chrome /opt/chrome/chrome; \
|
||||||
|
google-chrome --version
|
||||||
|
|
||||||
|
RUN set -eux; \
|
||||||
|
wget -qO /tmp/chromedriver.zip \
|
||||||
|
"https://storage.googleapis.com/chrome-for-testing-public/${CHROME_TESTING_VERSION}/linux64/chromedriver-linux64.zip"; \
|
||||||
|
unzip -q /tmp/chromedriver.zip -d /tmp; \
|
||||||
|
mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin; \
|
||||||
|
rm /tmp/chromedriver.zip; \
|
||||||
|
chmod +x /usr/local/bin/chromedriver; \
|
||||||
|
chromedriver --version
|
||||||
|
|
||||||
RUN chmod +x /opt/chrome/chrome
|
RUN chmod +x /opt/chrome/chrome
|
||||||
# Install dependencies
|
|
||||||
|
RUN pip3 install --upgrade pip setuptools wheel
|
||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
RUN mkdir -p /opt/workspace
|
||||||
|
RUN mkdir -p /tmp && chmod 1777 /tmp
|
||||||
|
|
||||||
# Copy application code
|
# Copy application code
|
||||||
COPY api.py .
|
COPY api.py .
|
||||||
COPY sources/ ./sources/
|
COPY sources/ ./sources/
|
||||||
COPY prompts/ ./prompts/
|
COPY prompts/ ./prompts/
|
||||||
COPY crx/ crx/
|
COPY crx/ crx/
|
||||||
COPY llm_router/ llm_router/
|
COPY llm_router/ llm_router/
|
||||||
COPY .env .
|
|
||||||
COPY config.ini .
|
COPY config.ini .
|
||||||
|
|
||||||
# Expose port
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Run the application
|
# Run the application
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
|
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
||||||
|
|
||||||
### Why AgenticSeek ?
|
### Why AgenticSeek ?
|
||||||
|
|
||||||
@@ -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.
|
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.
|
||||||
|
|
||||||
|
> 🙏 Please also understand that this project began as a side experiment, with no roadmap and no expectations, we didn't expect to end in Github trending. Financial backing is exactly $1/month (shoutout to my single sponsor). Contributions, feedback, and patience are deeply appreciated.
|
||||||
|
|
||||||
## Installation
|
## 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 advise you use exactly python3.10 for the setup. Dependencies error might happen otherwise.
|
||||||
|
|
||||||
For issues related to chrome driver, see the **Chromedriver** section.
|
For issues related to chrome driver, see the **Chromedriver** section.
|
||||||
|
|
||||||
@@ -58,26 +62,71 @@ source agentic_seek_env/bin/activate
|
|||||||
|
|
||||||
### 3️⃣ **Install package**
|
### 3️⃣ **Install package**
|
||||||
|
|
||||||
**Automatic Installation:**
|
Ensure Python, Docker and docker compose, and Google chrome are installed.
|
||||||
|
|
||||||
|
We recommend Python 3.10.0.
|
||||||
|
|
||||||
|
**Automatic Installation (recommended):**
|
||||||
|
|
||||||
|
For Linux/Macos:
|
||||||
```sh
|
```sh
|
||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
**Manually:**
|
For windows:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip3 install -r requirements.txt
|
./install.bat
|
||||||
# or
|
|
||||||
python3 setup.py install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Manually:**
|
||||||
|
|
||||||
|
**Note: For any OS, ensure the ChromeDriver you install matches your installed Chrome version. Run `google-chrome --version`. See known issues if you have chrome >135**
|
||||||
|
|
||||||
|
- *Linux*:
|
||||||
|
|
||||||
|
Update Package List: `sudo apt update`
|
||||||
|
|
||||||
|
Install Dependencies: `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
|
|
||||||
|
Install ChromeDriver matching your Chrome browser version:
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Macos*:
|
||||||
|
|
||||||
|
Update brew : `brew update`
|
||||||
|
|
||||||
|
Install chromedriver : `brew install --cask chromedriver`
|
||||||
|
|
||||||
|
Install portaudio: `brew install portaudio`
|
||||||
|
|
||||||
|
Upgrade pip : `python3 -m pip install --upgrade pip`
|
||||||
|
|
||||||
|
Upgrade wheel : : `pip3 install --upgrade setuptools wheel`
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Windows*:
|
||||||
|
|
||||||
|
Install pyreadline3 `pip install pyreadline3`
|
||||||
|
|
||||||
|
Install portaudio manually (e.g., via vcpkg or prebuilt binaries) and then run: `pip install pyaudio`
|
||||||
|
|
||||||
|
Download and install chromedriver manually from: https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
Place chromedriver in a directory included in your PATH.
|
||||||
|
|
||||||
|
Install requirements: `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Setup for running LLM locally on your machine
|
## Setup for running LLM locally on your machine
|
||||||
|
|
||||||
**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**
|
**Setup your local provider**
|
||||||
|
|
||||||
@@ -91,26 +140,40 @@ See below for a list of local supported provider.
|
|||||||
|
|
||||||
**Update the config.ini**
|
**Update the config.ini**
|
||||||
|
|
||||||
Change the config.ini file to set the provider_name to a supported provider and provider_model to `deepseek-r1:14b`
|
Change the config.ini file to set the provider_name to a supported provider and provider_model to a LLM supported by your provider. We recommend reasoning model such as *Qwen* or *Deepseek*.
|
||||||
|
|
||||||
NOTE: `deepseek-r1:14b`is an example, use a bigger model if your hardware allow it.
|
See the **FAQ** at the end of the README for required hardware.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # Whenever you are running locally or with remote provider.
|
||||||
provider_name = ollama # or lm-studio, openai, etc..
|
provider_name = ollama # or lm-studio, openai, etc..
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # choose a model that fit your hardware
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # name of your AI
|
||||||
|
recover_last_session = True # whenever to recover the previous session
|
||||||
|
save_session = True # whenever to remember the current session
|
||||||
|
speak = True # text to speech
|
||||||
|
listen = False # Speech to text, only for CLI
|
||||||
|
work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek.
|
||||||
|
jarvis_personality = False # Whenever to use a more "Jarvis" like personality (experimental)
|
||||||
|
languages = en zh # The list of languages, Text to speech will default to the first language on the list
|
||||||
|
[BROWSER]
|
||||||
|
headless_browser = True # Whenever to use headless browser, recommended 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**
|
**List of local providers**
|
||||||
|
|
||||||
| Provider | Local? | Description |
|
| Provider | Local? | Description |
|
||||||
|-----------|--------|-----------------------------------------------------------|
|
|-----------|--------|-----------------------------------------------------------|
|
||||||
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
|
||||||
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
|
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
|
||||||
| openai | Yes | Use openai compatible API |
|
| openai | Yes | Use openai compatible API (eg: llama.cpp server) |
|
||||||
|
|
||||||
|
|
||||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||||
|
|
||||||
@@ -124,21 +187,34 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
|||||||
|
|
||||||
## Setup to run with an API
|
## Setup to run with an API
|
||||||
|
|
||||||
Set the desired provider in the `config.ini`
|
Set the desired provider in the `config.ini`. See below for a list of API providers.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = False
|
is_local = False
|
||||||
provider_name = openai
|
provider_name = google
|
||||||
provider_model = gpt-4o
|
provider_model = gemini-2.0-flash
|
||||||
provider_server_address = 127.0.0.1:5000
|
provider_server_address = 127.0.0.1:5000 # doesn't matter
|
||||||
```
|
```
|
||||||
|
Warning: Make sure there is not trailing space in the config.
|
||||||
|
|
||||||
WARNING: Make sure there is not trailing space in the config.
|
Export your API key: `export <<PROVIDER>>_API_KEY="xxx"`
|
||||||
|
|
||||||
Set `is_local` to True if using a local openai-based api.
|
Example: export `TOGETHER_API_KEY="xxxxx"`
|
||||||
|
|
||||||
Change the IP address if your openai-based api run on your own server.
|
**List of API providers**
|
||||||
|
|
||||||
|
| Provider | Local? | Description |
|
||||||
|
|-----------|--------|-----------------------------------------------------------|
|
||||||
|
| openai | Depends | Use ChatGPT API |
|
||||||
|
| deepseek | 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 advise 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 seems to ignore our prompt for format to respect, which are optimized for deepseek r1.
|
||||||
|
|
||||||
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
||||||
|
|
||||||
@@ -171,6 +247,8 @@ start ./start_services.cmd # Window
|
|||||||
python3 cli.py
|
python3 cli.py
|
||||||
```
|
```
|
||||||
|
|
||||||
|
We advise you set `headless_browser` to False in the config.ini for CLI mode.
|
||||||
|
|
||||||
**Options 2:** Run with the Web interface.
|
**Options 2:** Run with the Web interface.
|
||||||
|
|
||||||
Start the backend.
|
Start the backend.
|
||||||
@@ -185,52 +263,30 @@ Go to `http://localhost:3000/` and you should see the web interface.
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 main.py`
|
Make sure the services are up and running with `./start_services.sh` and run the AgenticSeek with `python3 cli.py` for CLI mode or `python3 api.py` then go to `localhost:3000` for web interface.
|
||||||
|
|
||||||
```sh
|
You can also use speech to text by setting `listen = True` in the config. Only for CLI mode.
|
||||||
sudo ./start_services.sh
|
|
||||||
python3 cli.py
|
|
||||||
```
|
|
||||||
|
|
||||||
You will be prompted with `>>> `
|
To exit, simply say/type `goodbye`.
|
||||||
This indicate AgenticSeek await you type for instructions.
|
|
||||||
You can also use speech to text by setting `listen = True` in the config.
|
|
||||||
|
|
||||||
To exit, simply say `goodbye`.
|
|
||||||
|
|
||||||
Here are some example usage:
|
Here are some example usage:
|
||||||
|
|
||||||
### Coding/Bash
|
> *Make a snake game in python!*
|
||||||
|
|
||||||
> *Make a snake game in python*
|
> *Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.*
|
||||||
|
|
||||||
> *Show me how to multiply matrice in C*
|
> *Write a Go program to calculate the factorial of a number, save it as factorial.go in your workspace*
|
||||||
|
|
||||||
> *Make a blackjack in golang*
|
> *Search my summer_pictures folder for all JPG files, rename them with today’s date, and save a list of renamed files in photos_list.txt*
|
||||||
|
|
||||||
### Web search
|
> *Search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt.*
|
||||||
|
|
||||||
> *Do a web search to find cool tech startup in Japan working on cutting edge AI research*
|
> *Search the web for the latest AI news articles from 2025, select three, and write a Python script to scrape their titles and summaries. Save the script as news_scraper.py and the summaries in ai_news.txt in /home/projects*
|
||||||
|
|
||||||
> *Can you find on the internet who created AgenticSeek?*
|
> *Friday, search the web for a free stock price API, register with supersuper7434567@gmail.com then write a Python script to fetch using the API daily prices for Tesla, and save the results in stock_prices.csv*
|
||||||
|
|
||||||
> *Can you use a fuel calculator online to estimate the cost of a Nice - Milan trip*
|
*Note that form filling capabilities are still experimental and might fail.*
|
||||||
|
|
||||||
### File system
|
|
||||||
|
|
||||||
> *Hey can you find where is contract.pdf i lost it*
|
|
||||||
|
|
||||||
> *Show me how much space I have left on my disk*
|
|
||||||
|
|
||||||
> *Can you follow the readme and install project at /home/path/project*
|
|
||||||
|
|
||||||
### Casual
|
|
||||||
|
|
||||||
> *Tell me about Rennes, France*
|
|
||||||
|
|
||||||
> *Should I pursue a phd ?*
|
|
||||||
|
|
||||||
> *What's the best workout routine ?*
|
|
||||||
|
|
||||||
|
|
||||||
After you type your query, AgenticSeek will allocate the best agent for the task.
|
After you type your query, AgenticSeek will allocate the best agent for the task.
|
||||||
@@ -247,9 +303,9 @@ Instead, ask:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **Bonus: Setup to run the LLM on your own server**
|
## **Setup to run the LLM on your own server**
|
||||||
|
|
||||||
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server.
|
If you have a powerful computer or a server that you can use, but you want to use it from your laptop you have the options to run the LLM on a remote server using our custom llm server.
|
||||||
|
|
||||||
On your "server" that will run the AI model, get the ip address
|
On your "server" that will run the AI model, get the ip address
|
||||||
|
|
||||||
@@ -265,7 +321,7 @@ Clone the repository and enter the `server/`folder.
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||||
cd agenticSeek/server/
|
cd agenticSeek/llm_server/
|
||||||
```
|
```
|
||||||
|
|
||||||
Install server specific requirements:
|
Install server specific requirements:
|
||||||
@@ -303,6 +359,8 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
|
|||||||
|
|
||||||
## Speech to Text
|
## Speech to Text
|
||||||
|
|
||||||
|
Please note that currently speech to text only work in english.
|
||||||
|
|
||||||
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
|
The speech-to-text functionality is disabled by default. To enable it, set the listen option to True in the config.ini file:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -393,6 +451,7 @@ The table below show the available providers:
|
|||||||
| deepseek-api | No | Deepseek API (non-private) |
|
| deepseek-api | No | Deepseek API (non-private) |
|
||||||
| huggingface| No | Hugging-Face API (non-private) |
|
| huggingface| No | Hugging-Face API (non-private) |
|
||||||
| togetherAI | No | Use together AI API (non-private) |
|
| togetherAI | No | Use together AI API (non-private) |
|
||||||
|
| google | No | Use google gemini API (non-private) |
|
||||||
|
|
||||||
To select a provider change the config.ini:
|
To select a provider change the config.ini:
|
||||||
|
|
||||||
@@ -435,6 +494,27 @@ And download the chromedriver version matching your OS.
|
|||||||
|
|
||||||
If this section is incomplete please raise an issue.
|
If this section is incomplete please raise an issue.
|
||||||
|
|
||||||
|
## connection adapters Issues
|
||||||
|
|
||||||
|
```
|
||||||
|
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions'
|
||||||
|
```
|
||||||
|
|
||||||
|
Make sure you have `http://` in front of the provider IP address :
|
||||||
|
|
||||||
|
`provider_server_address = http://127.0.0.1:11434`
|
||||||
|
|
||||||
|
## SearxNG base URL must be provided
|
||||||
|
|
||||||
|
```
|
||||||
|
raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.")
|
||||||
|
ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.
|
||||||
|
```
|
||||||
|
|
||||||
|
Maybe you didn't move `.env.example` as `.env` ? You can also export SEARXNG_BASE_URL:
|
||||||
|
|
||||||
|
`export SEARXNG_BASE_URL="http://127.0.0.1:8080"`
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
**Q: What hardware do I need?**
|
**Q: What hardware do I need?**
|
||||||
@@ -461,7 +541,7 @@ Yes with Ollama, lm-studio or server providers, all speech to text, LLM and text
|
|||||||
**Q: Why should I use AgenticSeek when I have Manus?**
|
**Q: Why should I use AgenticSeek when I have Manus?**
|
||||||
|
|
||||||
This started as Side-Project we did out of interest about AI agents. What’s special about it is that we want to use local model and avoid APIs.
|
This started as Side-Project we did out of interest about AI agents. What’s special about it is that we want to use local model and avoid APIs.
|
||||||
We draw inspiration from Jarvis and Friday (Iron man movies) to make it "cool" but for functionnality we take more inspiration from Manus, because that's what people want in the first place: a local manus alternative.
|
We draw inspiration from Jarvis and Friday (Iron man movies) to make it "cool" but for functionality we take more inspiration from Manus, because that's what people want in the first place: a local manus alternative.
|
||||||
Unlike Manus, AgenticSeek prioritizes independence from external systems, giving you more control, privacy and avoid api cost.
|
Unlike Manus, AgenticSeek prioritizes independence from external systems, giving you more control, privacy and avoid api cost.
|
||||||
|
|
||||||
## Contribute
|
## Contribute
|
||||||
@@ -473,5 +553,13 @@ We’re looking for developers to improve AgenticSeek! Check out open issues or
|
|||||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||||
|
|
||||||
## Maintainers:
|
## Maintainers:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [Fosowl](https://github.com/Fosowl) | Paris Time
|
||||||
|
|
||||||
|
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||||
|
|
||||||
|
> [steveh8758](https://github.com/steveh8758) | Taipei Time
|
||||||
|
|
||||||
|
## Special Thanks:
|
||||||
|
|
||||||
|
> [tcsenpai](https://github.com/tcsenpai) For dockerization of backend
|
||||||
|
|||||||
+100
-42
@@ -1,43 +1,32 @@
|
|||||||
|
# AgenticSeek: Private, Local Manus Alternative.
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img align="center" src="./media/whale_readme.jpg">
|
<img align="center" src="./media/whale_readme.jpg">
|
||||||
<p>
|
<p>
|
||||||
|
|
||||||
|
|
||||||
[English](./README.md) | 中文 | [日本語](./README_JP.md)
|
[English](./README.md) | 中文 | [日本語](./README_JP.md)
|
||||||
|
|
||||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。*
|
||||||
|
|
||||||
**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页,并自动修正错误与反省,最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460)
|
### 为什么选择 AgenticSeek?
|
||||||
|
|
||||||
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
|
* 🔒 完全本地化与隐私保护 - 所有功能都在您的设备上运行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
|
||||||
|
|
||||||
|
* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、阅读、提取信息、填写网页表单 — 全程无需人工操作。
|
||||||
|
|
||||||
|
* 💻 自主编码助手 - 需要代码?它可以编写、调试并运行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
|
||||||
|
|
||||||
|
* 🧠 智能代理选择 - 您提问,它会自动选择最适合该任务的代理。就像拥有一个随时待命的专家团队。
|
||||||
|
|
||||||
|
* 📋 规划与执行复杂任务 - 从旅行规划到复杂项目 — 它能将大型任务分解为步骤,并利用多个 AI 代理完成工作。
|
||||||
|
|
||||||
|
* 🎙️ 语音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,让您能像科幻电影中一样与您的个人 AI 助手对话。
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||||
|
|
||||||
> *在大阪和东京深入搜寻人工智慧新创公司,至少找到 5 家,然后储存在 research_japan.txt 档案中*
|
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
|
||||||
|
|
||||||
> *你可以用 C 语言制作俄罗斯方块游戏吗?*
|
|
||||||
|
|
||||||
> *我想设定一个新的专案档案索引,命名为 mark2。*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Features:
|
|
||||||
|
|
||||||
- **100% 本机运行**: 本机运行,不使用云端服务,所以资料绝不会散布出去,我的东西还是我的!不会被当作其他服务的训练资料。
|
|
||||||
|
|
||||||
- **文件的交互系统**: 使用 bash 去浏览本机资料和操作本机系统。
|
|
||||||
|
|
||||||
- **自主 Coding**: AgenticSeek 可以自己运行、Debug、编译 Python、C、Golang 和各种语言。
|
|
||||||
|
|
||||||
- **代理助理**: 不同的工作由不同的助理去处理问题。AgenticSeek 会自己寻找最适合的助理去做相对应的工作。
|
|
||||||
|
|
||||||
- **规划**: 对于复杂的任务,AgenticSeek 会交办给不同的助理进行规划和执行。
|
|
||||||
|
|
||||||
- **自主学习**: 自动在网路上寻找资料。
|
|
||||||
|
|
||||||
- **记忆功能**: 对于每次的对话进行统整、保存对话,并且在本地储存用户的使用习惯。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -45,6 +34,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
|||||||
|
|
||||||
确保已安装了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
确保已安装了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
||||||
|
|
||||||
|
我们强烈建议您使用 Python 3.10 进行设置,否则可能会发生依赖错误。
|
||||||
|
|
||||||
有关于 Chrome driver 的问题,请参见 **Chromedriver** 部分。
|
有关于 Chrome driver 的问题,请参见 **Chromedriver** 部分。
|
||||||
|
|
||||||
### 1️⃣ **复制储存库与设置环境变数**
|
### 1️⃣ **复制储存库与设置环境变数**
|
||||||
@@ -71,13 +62,55 @@ source agentic_seek_env/bin/activate
|
|||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
|
||||||
|
|
||||||
|
```
|
||||||
|
pip3 install jieba cn2an
|
||||||
|
```
|
||||||
|
|
||||||
**手动安装:**
|
**手动安装:**
|
||||||
|
|
||||||
```sh
|
|
||||||
pip3 install -r requirements.txt
|
**注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题**
|
||||||
# or
|
|
||||||
python3 setup.py install
|
- *Linux*:
|
||||||
```
|
|
||||||
|
更新软件包列表:`sudo apt update`
|
||||||
|
|
||||||
|
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
|
|
||||||
|
安装与您的 Chrome 浏览器版本匹配的 ChromeDriver:
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Macos*:
|
||||||
|
|
||||||
|
更新 brew:`brew update`
|
||||||
|
|
||||||
|
安装 chromedriver:`brew install --cask chromedriver`
|
||||||
|
|
||||||
|
安装 portaudio:`brew install portaudio`
|
||||||
|
|
||||||
|
升级 pip:`python3 -m pip install --upgrade pip`
|
||||||
|
|
||||||
|
升级 wheel:`pip3 install --upgrade setuptools wheel`
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Windows*:
|
||||||
|
|
||||||
|
安装 pyreadline3:`pip install pyreadline3`
|
||||||
|
|
||||||
|
手动安装 portaudio(例如,通过 vcpkg 或预编译的二进制文件),然后运行:`pip install pyaudio`
|
||||||
|
|
||||||
|
从以下网址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
将 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 在本地机器上运行 AgenticSeek
|
## 在本地机器上运行 AgenticSeek
|
||||||
|
|
||||||
@@ -93,18 +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
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # 无论是在本地运行还是使用远程提供者。
|
||||||
provider_name = ollama # 或 lm-studio, openai 等
|
provider_name = ollama # 或 lm-studio, openai 等..
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # 您的 AI 助手的名称
|
||||||
|
recover_last_session = True # 是否恢复之前的会话
|
||||||
|
save_session = True # 是否记住当前会话
|
||||||
|
speak = True # 文本转语音
|
||||||
|
listen = False # 语音转文本,仅适用于命令行界面
|
||||||
|
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
|
||||||
|
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
|
||||||
|
languages = en zh # 语言列表,文本转语音将默认使用列表中的第一种语言
|
||||||
|
[BROWSER]
|
||||||
|
headless_browser = True # 是否使用无头浏览器,只有在使用网页界面时才推荐使用。
|
||||||
|
stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测
|
||||||
```
|
```
|
||||||
|
|
||||||
|
警告:使用 LM-studio 运行 LLM 时,请*不要*将 provider_name 设置为 `openai`。请将其设置为 `lm-studio`。
|
||||||
|
|
||||||
|
注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**本地提供者列表**
|
**本地提供者列表**
|
||||||
|
|
||||||
| 提供者 | 本地? | 描述 |
|
| 提供者 | 本地? | 描述 |
|
||||||
@@ -313,6 +365,8 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
## 语音转文字
|
## 语音转文字
|
||||||
|
|
||||||
|
请注意,目前语音转文字功能仅支持英语。
|
||||||
|
|
||||||
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`:
|
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -469,7 +523,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
|||||||
|
|
||||||
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
|
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
|
||||||
|
|
||||||
**Q:我在执行时 `main.py` 时出现错误。我该怎么办?**
|
**Q:我在执行时 `cli.py` 时出现错误。我该怎么办?**
|
||||||
|
|
||||||
1. 确保 Ollama 正在运行(ollama serve)
|
1. 确保 Ollama 正在运行(ollama serve)
|
||||||
2. 你 `config.ini` 内 `provider_name` 的框架选择正确。
|
2. 你 `config.ini` 内 `provider_name` 的框架选择正确。
|
||||||
@@ -508,6 +562,10 @@ DeepSeek R1 天生会说中文
|
|||||||
|
|
||||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 作者:
|
## 维护者:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [Fosowl](https://github.com/Fosowl) | 巴黎时间
|
||||||
|
|
||||||
|
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||||
|
|
||||||
|
> [steveh8758](https://github.com/steveh8758) | 台北时间
|
||||||
|
|||||||
+109
-60
@@ -1,3 +1,5 @@
|
|||||||
|
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img align="center" src="./media/whale_readme.jpg">
|
<img align="center" src="./media/whale_readme.jpg">
|
||||||
<p>
|
<p>
|
||||||
@@ -5,40 +7,28 @@
|
|||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
|
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
|
||||||
|
|
||||||
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
|
|
||||||
|
|
||||||
|
*一个 **100% 本地替代 Manus AI** 的方案,這款支持語音的 AI 助理能够自主瀏覽網頁、编寫代码和規劃任務,同时將所有用戶資料保留在您的裝置上。專門為本地推理模型量身打造,完全在您自己的硬體上執行,确保完全的隐私保护和零雲端依賴。*
|
||||||
|
|
||||||
**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁,並自動修正錯誤與反省,最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460)
|
### 为什么選擇 AgenticSeek?
|
||||||
|
|
||||||
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
|
* 🔒 完全本地化與隐私保护 - 所有功能都在您的设备上運行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
|
||||||
|
|
||||||
|
* 🌐 智能網頁瀏覽 - AgenticSeek 能够自主瀏覽網頁 — 搜索、閱读、提取信息、填寫網页表單 — 全程无需人工操作。
|
||||||
|
|
||||||
|
* 💻 自主编码助手 - 需要代码?它可以编寫、调试并運行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
|
||||||
|
|
||||||
|
* 🧠 智能代理选择 - 您提问,它會自动选择最适合该任务的代理。就像拥有一个随时待命的專家团队。
|
||||||
|
|
||||||
|
* 📋 规划與执行复杂任务 - 从旅行规划到复杂项目 — 它能將大型任务分解为步骤,并利用多个 AI 代理完成工作。
|
||||||
|
|
||||||
|
* 🎙️ 語音功能 - 清晰、快速、未来感十足的語音與語音轉文本功能,讓您能像科幻电影中一样與您的个人 AI 助手对话。
|
||||||
|
|
||||||
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
||||||
|
|
||||||
> *在大阪和東京深入搜尋人工智慧新創公司,至少找到 5 家,然後儲存在 research_japan.txt 檔案中*
|
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
|
||||||
|
|
||||||
> *你可以用 C 語言製作俄羅斯方塊遊戲嗎?*
|
|
||||||
|
|
||||||
> *我想設定一個新的專案檔案索引,命名為 mark2。*
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Features:
|
|
||||||
|
|
||||||
- **100% 本機運行**: 本機運行,不使用雲端服務,所以資料絕不會散布出去,我的東西還是我的!不會被當作其他服務的訓練資料。
|
|
||||||
|
|
||||||
- **文件的交互系統**: 使用 bash 去瀏覽本機資料和操作本機系統。
|
|
||||||
|
|
||||||
- **自主 Coding**: AgenticSeek 可以自己運行、Debug、編譯 Python、C、Golang 和各種語言。
|
|
||||||
|
|
||||||
- **代理助理**: 不同的工作由不同的助理去處理問題。AgenticSeek 會自己尋找最適合的助理去做相對應的工作。
|
|
||||||
|
|
||||||
- **規劃**: 對於複雜的任務,AgenticSeek 會交辦給不同的助理進行規劃和執行。
|
|
||||||
|
|
||||||
- **自主學習**: 自動在網路上尋找資料。
|
|
||||||
|
|
||||||
- **記憶功能**: 對於每次的對話進行統整、保存對話,並且在本地儲存用戶的使用習慣。
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,6 +36,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
|||||||
|
|
||||||
確保已安裝了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
確保已安裝了 Chrome driver,Docker 和 Python 3.10(或更新)。
|
||||||
|
|
||||||
|
我们强烈建议您使用 Python 3.10 進行設定,否则可能會发生依赖错误。
|
||||||
|
|
||||||
有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。
|
有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。
|
||||||
|
|
||||||
### 1️⃣ **複製儲存庫與設置環境變數**
|
### 1️⃣ **複製儲存庫與設置環境變數**
|
||||||
@@ -72,46 +64,97 @@ source agentic_seek_env/bin/activate
|
|||||||
./install.sh
|
./install.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
** 若要將文字轉成語音(TTS)功能支持中文,你需要安装 jieba(中文分詞庫)和 cn2an(中文數字轉換庫):**
|
||||||
|
|
||||||
|
```
|
||||||
|
pip3 install jieba cn2an
|
||||||
|
```
|
||||||
|
|
||||||
**手動安裝:**
|
**手動安裝:**
|
||||||
|
|
||||||
```sh
|
|
||||||
pip3 install -r requirements.txt
|
**注意:對於不同作業系統,請確保已經安装的 ChromeDriver 與您已安装的 Chrome 版本一致。可以執行 `google-chrome --version`。如果您的 Chrome 版本 > 135,請參考已知问题**
|
||||||
# or
|
|
||||||
python3 setup.py install
|
- *Linux*:
|
||||||
```
|
|
||||||
|
更新软件包列表:`sudo apt update`
|
||||||
|
|
||||||
|
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
|
|
||||||
|
安装與您的 Chrome 瀏覽器版本匹配的 ChromeDriver:
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Macos*:
|
||||||
|
|
||||||
|
更新 brew:`brew update`
|
||||||
|
|
||||||
|
安装 chromedriver:`brew install --cask chromedriver`
|
||||||
|
|
||||||
|
安装 portaudio:`brew install portaudio`
|
||||||
|
|
||||||
|
升级 pip:`python3 -m pip install --upgrade pip`
|
||||||
|
|
||||||
|
升级 wheel:`pip3 install --upgrade setuptools wheel`
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Windows*:
|
||||||
|
|
||||||
|
安装 pyreadline3:`pip install pyreadline3`
|
||||||
|
|
||||||
|
手动安装 portaudio(例如,通过 vcpkg 或預編譯的二進制文件),然後運行:`pip install pyaudio`
|
||||||
|
|
||||||
|
从以下網址手动下载并安装 chromedriver:https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
將 chromedriver 放置在包含在您的 PATH 中的目录中。
|
||||||
|
|
||||||
|
安装 requirements:`pip3 install -r requirements.txt`
|
||||||
|
|
||||||
## 在本地機器上運行 AgenticSeek
|
## 在本地機器上運行 AgenticSeek
|
||||||
|
|
||||||
**建議至少使用 Deepseek 14B 以上參數的模型,較小的模型難以使用助理功能並且很快就會忘記上下文之間的關係。**
|
**建議至少使用 Deepseek 14B 以上參數的模型,較小的模型難以使用助理功能並且很快就會忘記上下文之間的關係。**
|
||||||
|
|
||||||
**本地运行助手**
|
**本地運行助手**
|
||||||
|
|
||||||
启动你的本地提供者,例如使用 ollama:
|
啟動你的本地提供者,例如使用 ollama:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ollama serve
|
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
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # 无论是在本地運行还是使用远程提供者。
|
||||||
provider_name = ollama # 或 lm-studio, openai 等
|
provider_name = ollama # 或 lm-studio, openai 等..
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # 您的 AI 助手的名称
|
||||||
|
recover_last_session = True # 是否恢复之前的會话
|
||||||
|
save_session = True # 是否记住当前會话
|
||||||
|
speak = True # 文本轉語音
|
||||||
|
listen = False # 語音轉文本,僅适用于命令行界面
|
||||||
|
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
|
||||||
|
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
|
||||||
|
languages = en zh # 语言列表,文本轉語音將默认使用列表中的第一种语言
|
||||||
|
[BROWSER]
|
||||||
|
headless_browser = True # 是否使用无头瀏覽器,只有在使用網页界面时才推荐使用。
|
||||||
|
stealth_mode = True # 使用无法檢測的 selenium 来减少瀏覽器檢測
|
||||||
```
|
```
|
||||||
|
|
||||||
**本地提供者列表**
|
**本地提供者列表**
|
||||||
|
|
||||||
| 提供者 | 本地? | 描述 |
|
| 提供者 | 本地? | 描述 |
|
||||||
|-------------|--------|-------------------------------------------------------|
|
|-------------|--------|-------------------------------------------------------|
|
||||||
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地运行 LLM |
|
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地運行 LLM |
|
||||||
| lm-studio | 是 | 使用 LM Studio 本地运行 LLM(将 `provider_name` 设置为 `lm-studio`)|
|
| lm-studio | 是 | 使用 LM Studio 本地運行 LLM(將 `provider_name` 設定为 `lm-studio`)|
|
||||||
| openai | 否 | 使用兼容的 API |
|
| openai | 否 | 使用兼容的 API |
|
||||||
|
|
||||||
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
|
下一步: [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
|
## Start services and Run
|
||||||
(启动服务并运行)
|
(啟動服务并運行)
|
||||||
|
|
||||||
如果需要,请激活你的 Python 环境。
|
如果需要,请激活你的 Python 环境。
|
||||||
```sh
|
```sh
|
||||||
source agentic_seek_env/bin/activate
|
source agentic_seek_env/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
启动所需的服务。这将启动 `docker-compose.yml` 中的所有服务,包括:
|
啟動所需的服务。这將啟動 `docker-compose.yml` 中的所有服务,包括:
|
||||||
- searxng
|
- searxng
|
||||||
- redis(由 redis 提供支持)
|
- redis(由 redis 提供支持)
|
||||||
- 前端
|
- 前端
|
||||||
@@ -158,25 +201,25 @@ sudo ./start_services.sh # MacOS
|
|||||||
start ./start_services.cmd # Windows
|
start ./start_services.cmd # Windows
|
||||||
```
|
```
|
||||||
|
|
||||||
**选项 1:** 使用 CLI 界面运行。
|
**選項 1:** 使用 CLI 界面運行。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 cli.py
|
python3 cli.py
|
||||||
```
|
```
|
||||||
|
|
||||||
**选项 2:** 使用 Web 界面运行。
|
**選項 2:** 使用 Web 界面運行。
|
||||||
|
|
||||||
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
|
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
|
||||||
|
|
||||||
启动后端服务。
|
啟動後端服务。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 api.py
|
python3 api.py
|
||||||
```
|
```
|
||||||
|
|
||||||
访问 `http://localhost:3000/`,你应该会看到 Web 界面。
|
访问 `http://localhost:3000/`,你应该會看到 Web 界面。
|
||||||
|
|
||||||
请注意,目前 Web 界面不支持消息流式传输。
|
请注意,目前 Web 界面不支持消息流式傳輸。
|
||||||
|
|
||||||
|
|
||||||
*如果你不知道如何開始,請參閱 **Usage** 部分*
|
*如果你不知道如何開始,請參閱 **Usage** 部分*
|
||||||
@@ -185,9 +228,9 @@ python3 api.py
|
|||||||
|
|
||||||
## Usage (使用方法)
|
## Usage (使用方法)
|
||||||
|
|
||||||
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中设置语言选项。
|
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中設定语言選項。
|
||||||
languages = en zh
|
languages = en zh
|
||||||
更多信息请参阅 Config 部分
|
更多信息请参閱 Config 部分
|
||||||
|
|
||||||
確定所有的核心檔案都啟用了,也就是執行過這條命令 `./start_services.sh` 然後你就可以使用 `python3 cli.py` 來啟動 AgenticSeek 了!
|
確定所有的核心檔案都啟用了,也就是執行過這條命令 `./start_services.sh` 然後你就可以使用 `python3 cli.py` 來啟動 AgenticSeek 了!
|
||||||
|
|
||||||
@@ -244,11 +287,11 @@ python3 cli.py
|
|||||||
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
|
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
|
||||||
|
|
||||||
你該說:
|
你該說:
|
||||||
- 进行网络搜索,找出哪些国家最适合独自旅行
|
- 進行網路搜索,找出哪些国家最适合獨自旅行
|
||||||
|
|
||||||
|
|
||||||
而不是說:
|
而不是說:
|
||||||
- 你知道哪些国家适合独自旅行?
|
- 你知道哪些国家适合獨自旅行?
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -314,6 +357,8 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
## 語音轉文字
|
## 語音轉文字
|
||||||
|
|
||||||
|
请注意,目前語音轉文字功能僅支援英语。
|
||||||
|
|
||||||
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -470,7 +515,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
|||||||
|
|
||||||
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
|
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
|
||||||
|
|
||||||
**Q:我在執行時 `main.py` 時出現錯誤。我該怎麼辦?**
|
**Q:我在執行時 `cli.py` 時出現錯誤。我該怎麼辦?**
|
||||||
|
|
||||||
1. 確保 Ollama 正在運行(ollama serve)
|
1. 確保 Ollama 正在運行(ollama serve)
|
||||||
2. 你 `config.ini` 內 `provider_name` 的框架選擇正確。
|
2. 你 `config.ini` 內 `provider_name` 的框架選擇正確。
|
||||||
@@ -493,13 +538,13 @@ https://googlechromelabs.github.io/chrome-for-testing/
|
|||||||
|
|
||||||
**Q: 是否支持中文以外的语言?**
|
**Q: 是否支持中文以外的语言?**
|
||||||
|
|
||||||
DeepSeek R1 天生会说中文
|
DeepSeek R1 天生會说中文
|
||||||
|
|
||||||
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
|
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
|
||||||
|
|
||||||
如果不设置中文?后果可能是:你让它写代码,结果跳出来个"医生代理"(虽然我们根本没有这个代理... 但系统会一脸懵圈!)
|
如果不設定中文?後果可能是:你讓它寫代码,结果跳出来个"醫生代理"(虽然我们根本没有这个代理... 但系统會一脸懵圈!)
|
||||||
|
|
||||||
实际上会下载一个小型翻译模型来协助任务分配
|
实际上會下载一个小型翻译模型来协助任务分配
|
||||||
|
|
||||||
## 貢獻
|
## 貢獻
|
||||||
|
|
||||||
@@ -509,6 +554,10 @@ DeepSeek R1 天生会说中文
|
|||||||
|
|
||||||
[Contribution guide](./docs/CONTRIBUTING.md)
|
[Contribution guide](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 作者:
|
## 维护者:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [Fosowl](https://github.com/Fosowl) | 巴黎時間 | (有时很忙)
|
||||||
|
|
||||||
|
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北時間 | (經常很忙)
|
||||||
|
|
||||||
|
> [steveh8758](https://github.com/steveh8758) | 台北時間 | (總是很忙)
|
||||||
|
|||||||
+77
-18
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données.
|
Une alternative **entièrement locale** à Manus AI, un assistant IA qui code, explore votre système de fichiers, navigue sur le web et corrige ses erreurs, tout cela sans envoyer la moindre donnée dans le cloud. Cet agent autonome fonctionne entièrement sur votre hardware, garantissant la confidentialité de vos données.
|
||||||
|
|
||||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/m37d7XxZ) [](https://x.com/Martin993886460)
|
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460)
|
||||||
|
|
||||||
> 🛠️ **En cours de développement** – On cherche activement des contributeurs!
|
> 🛠️ **En cours de développement** – On cherche activement des contributeurs!
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Fonctionnalités:
|
## Fonctionnalités:
|
||||||
|
|
||||||
- **100% Local**: Fonctionne en local sur votre PC. Vos données restent les vôtres.
|
- **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**
|
## **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.
|
Pour les problèmes liés au pilote Chrome, consultez la section Chromedriver.
|
||||||
|
|
||||||
@@ -74,9 +75,44 @@ source agentic_seek_env/bin/activate
|
|||||||
|
|
||||||
**Manuel:**
|
**Manuel:**
|
||||||
|
|
||||||
```sh
|
**Note : Pour tous les systèmes d'exploitation, assurez-vous que le ChromeDriver que vous installez correspond à la version de Chrome installée. Exécutez `google-chrome --version`. Consultez les problèmes connus si vous avez Chrome >135**
|
||||||
pip3 install -r requirements.txt
|
|
||||||
```
|
- *Linux*:
|
||||||
|
|
||||||
|
Mettre à jour la liste des paquets : `sudo apt update`
|
||||||
|
|
||||||
|
Installer les dépendances : `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
|
||||||
|
|
||||||
|
Installer ChromeDriver correspondant à la version de votre navigateur Chrome :
|
||||||
|
`sudo apt install -y chromium-chromedriver`
|
||||||
|
|
||||||
|
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *macOS*:
|
||||||
|
|
||||||
|
Mettre à jour brew : `brew update`
|
||||||
|
|
||||||
|
Installer chromedriver : `brew install --cask chromedriver`
|
||||||
|
|
||||||
|
Installer portaudio : `brew install portaudio`
|
||||||
|
|
||||||
|
Mettre à jour pip : `python3 -m pip install --upgrade pip`
|
||||||
|
|
||||||
|
Mettre à jour wheel : `pip3 install --upgrade setuptools wheel`
|
||||||
|
|
||||||
|
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
- *Windows*:
|
||||||
|
|
||||||
|
Installer pyreadline3 : `pip install pyreadline3`
|
||||||
|
|
||||||
|
Installer portaudio manuellement (par exemple, via vcpkg ou des binaires précompilés) puis exécutez : `pip install pyaudio`
|
||||||
|
|
||||||
|
Télécharger et installer chromedriver manuellement depuis : https://sites.google.com/chromium.org/driver/getting-started
|
||||||
|
|
||||||
|
Placez chromedriver dans un répertoire inclus dans votre PATH.
|
||||||
|
|
||||||
|
Installer les prérequis : `pip3 install -r requirements.txt`
|
||||||
|
|
||||||
|
|
||||||
## Faire fonctionner sur votre machine
|
## Faire fonctionner sur votre machine
|
||||||
@@ -88,18 +124,35 @@ Lancer votre provider local, par exemple avec ollama:
|
|||||||
ollama serve
|
ollama serve
|
||||||
```
|
```
|
||||||
|
|
||||||
Voyez la section **Provider** pour la liste de provideurs disponible.
|
**Configurer le config.ini**
|
||||||
|
|
||||||
Modifiez le fichier config.ini pour définir provider_name sur le nom d'un provideur et provider_model sur le LLM à utiliser.
|
Modifiez le fichier config.ini pour définir provider_name sur un fournisseur supporté et provider_model sur un LLM compatible avec votre fournisseur. Nous recommandons des modèles de raisonnement comme *Qwen* ou *Deepseek*.
|
||||||
|
|
||||||
|
Consultez la section **FAQ** à la fin du README pour connaître le matériel requis.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # Si vous exécutez localement ou avec un fournisseur distant.
|
||||||
provider_name = ollama # ou lm-studio, openai, etc...
|
provider_name = ollama # ou lm-studio, openai, etc..
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # choisissez un modèle adapté à votre matériel
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # nom de votre IA
|
||||||
|
recover_last_session = True # récupérer ou non la session précédente
|
||||||
|
save_session = True # mémoriser ou non la session actuelle
|
||||||
|
speak = True # synthèse vocale
|
||||||
|
listen = False # reconnaissance vocale, uniquement pour CLI
|
||||||
|
work_dir = /Users/mlg/Documents/workspace # L'espace de travail pour AgenticSeek.
|
||||||
|
jarvis_personality = False # Utiliser une personnalité plus "Jarvis", non recommandé avec des petits modèles
|
||||||
|
languages = en fr # Liste des langages, la synthèse vocale utilisera par défaut la première langue de la liste
|
||||||
|
[BROWSER]
|
||||||
|
headless_browser = True # Utiliser ou non le navigateur sans interface graphique, recommandé uniquement avec l'interface web.
|
||||||
|
stealth_mode = True # Utiliser selenium non détectable pour réduire la détection du navigateur
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Remarque : Certains fournisseurs (ex : lm-studio) nécessitent `http://` devant l'adresse IP. Par exemple `http://127.0.0.1:1234`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Liste des provideurs locaux**
|
**Liste des provideurs locaux**
|
||||||
|
|
||||||
| Fournisseur | Local ? | Description |
|
| Fournisseur | Local ? | Description |
|
||||||
@@ -274,22 +327,27 @@ provider_server_address = x.x.x.x:3333
|
|||||||
|
|
||||||
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
||||||
|
|
||||||
## **Exécuter avec une API**
|
## **Exécuter avec une API externe**
|
||||||
|
|
||||||
AVERTISSEMENT : Assurez-vous qu’il n’y a pas d’espace en fin de ligne dans la configuration.
|
AVERTISSEMENT : Assurez-vous qu’il n’y a pas d’espace en fin de ligne dans la configuration.
|
||||||
|
|
||||||
Définissez is_local sur True si vous utilisez une API basée sur OpenAI localement.
|
|
||||||
|
|
||||||
Changez l’adresse IP si votre API basée sur OpenAI fonctionne sur votre propre serveur.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = False
|
is_local = False
|
||||||
provider_name = openai
|
provider_name = openai
|
||||||
provider_model = gpt-4o
|
provider_model = gpt-4o
|
||||||
provider_server_address = 127.0.0.1:5000
|
provider_server_address = 127.0.0.1:5000 # n'importe pas
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Liste de provideurs API**
|
||||||
|
| Fournisseur | Local ? | Description |
|
||||||
|
|--------------|---------|-----------------------------------------------------------|
|
||||||
|
| openai | Non | Utilise l'API ChatGPT |
|
||||||
|
| deepseek-api | Non | API Deepseek (non privé) |
|
||||||
|
| huggingface | Non | API Hugging-Face (non privé) |
|
||||||
|
| togetherAI | Non | Utilise l'API Together AI (non privé) |
|
||||||
|
| google | Non | Utilise l'API Google Gemini (non privé) |
|
||||||
|
|
||||||
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
Ensuite, exécutez avec le CLI ou l'interface graphique comme expliqué dans la section pour les fournisseurs locaux.
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
@@ -434,6 +492,7 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
|
|||||||
|
|
||||||
[Guide du contributeur](./docs/CONTRIBUTING.md)
|
[Guide du contributeur](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Auteurs/Mainteneurs:
|
## Mainteneurs:
|
||||||
> [Fosowl](https://github.com/Fosowl)
|
> [Fosowl](https://github.com/Fosowl)
|
||||||
> [steveh8758](https://github.com/steveh8758)
|
> [steveh8758](https://github.com/steveh8758)
|
||||||
|
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
|
||||||
|
|||||||
+311
-219
@@ -1,61 +1,48 @@
|
|||||||
|
# AgenticSeek: プライベートなローカルManus代替
|
||||||
|
|
||||||
<p align="center">
|
<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>
|
<p>
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
|
||||||
[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語
|
[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/m37d7XxZ) [](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%ローカル**: クラウドなし、ハードウェア上で動作。データはあなたのものです。
|
### 1️⃣ **リポジトリのクローンとセットアップ**
|
||||||
|
|
||||||
- **ファイルシステムの操作**: bashを使用してファイルを簡単にナビゲートおよび操作します。
|
|
||||||
|
|
||||||
- **自律的なコーディング**: Python、C、Golangなどのコードを書き、デバッグし、実行できます。
|
|
||||||
|
|
||||||
- **エージェントルーティング**: タスクに最適なエージェントを自動的に選択します。
|
|
||||||
|
|
||||||
- **計画**: 複雑なタスクの場合、複数のエージェントを起動して計画および実行します。
|
|
||||||
|
|
||||||
- **自律的なウェブブラウジング**: 自律的なウェブナビゲーション。
|
|
||||||
|
|
||||||
- **メモリ**: 効率的なメモリとセッション管理。
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **インストール**
|
|
||||||
|
|
||||||
chrome driver、docker、およびpython3.10(またはそれ以降)がインストールされていることを確認してください。
|
|
||||||
|
|
||||||
chrome driverに関連する問題については、**Chromedriver**セクションを参照してください。
|
|
||||||
|
|
||||||
### 1️⃣ **リポジトリをクローンしてセットアップ**
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/Fosowl/agenticSeek.git
|
git clone https://github.com/Fosowl/agenticSeek.git
|
||||||
@@ -63,7 +50,7 @@ cd agenticSeek
|
|||||||
mv .env.example .env
|
mv .env.example .env
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2️ **仮想環境を作成**
|
### 2️ **仮想環境の作成**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 -m venv agentic_seek_env
|
python3 -m venv agentic_seek_env
|
||||||
@@ -71,91 +58,176 @@ source agentic_seek_env/bin/activate
|
|||||||
# Windowsの場合: agentic_seek_env\Scripts\activate
|
# Windowsの場合: agentic_seek_env\Scripts\activate
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3️⃣ **パッケージをインストール**
|
### 3️⃣ **パッケージのインストール**
|
||||||
|
|
||||||
**自動インストール:**
|
Python、Dockerとdocker compose、Google Chromeがインストールされていることを確認してください。
|
||||||
|
|
||||||
|
Python 3.10.0を推奨します。
|
||||||
|
|
||||||
|
**自動インストール(推奨):**
|
||||||
|
|
||||||
|
Linux/Macosの場合:
|
||||||
```sh
|
```sh
|
||||||
./install.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
|
```sh
|
||||||
pip3 install -r requirements.txt
|
./install.bat
|
||||||
# または
|
|
||||||
python3 setup.py install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**手動:**
|
||||||
|
|
||||||
|
**注意:どの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
|
```sh
|
||||||
ollama serve
|
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
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True # ローカルで実行するか、リモートプロバイダーで実行するか。
|
||||||
provider_name = ollama # または lm-studio、openai など
|
provider_name = ollama # またはlm-studio、openaiなど。
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b # ハードウェアに合ったモデルを選択してください
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
|
agent_name = Jarvis # AIの名前
|
||||||
|
recover_last_session = True # 前のセッションを復元するかどうか
|
||||||
|
save_session = True # 現在のセッションを記憶するかどうか
|
||||||
|
speak = True # テキスト読み上げ
|
||||||
|
listen = False # 音声認識、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を簡単に実行 |
|
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
|
||||||
| lm-studio | はい | LM studioを使用してローカルでLLMを実行(`provider_name`を`lm-studio`に設定)|
|
| lm-studio | はい | LM studioでLLMをローカル実行します(`provider_name`を`lm-studio`に設定)|
|
||||||
| openai | はい | OpenAI互換APIを使用 |
|
| openai | はい | openai互換API(例:llama.cppサーバー)を使用します |
|
||||||
|
|
||||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
|
||||||
|
|
||||||
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
|
*問題が発生した場合は、**既知の問題**セクションを参照してください*
|
||||||
|
|
||||||
*ハードウェアがDeepseekをローカルで実行できない場合は、**APIを使用した実行**セクションを参照してください。*
|
*ハードウェアがローカルでdeepseekを実行できない場合は、**APIで実行**セクションを参照してください*
|
||||||
|
|
||||||
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## APIを使用したセットアップ
|
## APIで実行するためのセットアップ
|
||||||
|
|
||||||
`config.ini`で希望するプロバイダーを設定してください。
|
`config.ini`で目的のプロバイダーを設定します。APIプロバイダーのリストについては、以下を参照してください。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = False
|
is_local = False
|
||||||
provider_name = openai
|
provider_name = google
|
||||||
provider_model = gpt-4o
|
provider_model = gemini-2.0-flash
|
||||||
provider_server_address = 127.0.0.1:5000
|
provider_server_address = 127.0.0.1:5000 # 関係ありません
|
||||||
```
|
```
|
||||||
|
警告:設定に末尾のスペースがないことを確認してください。
|
||||||
|
|
||||||
警告: `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
|
```sh
|
||||||
source agentic_seek_env/bin/activate
|
source agentic_seek_env/bin/activate
|
||||||
```
|
```
|
||||||
|
|
||||||
必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます:
|
必要なサービスを開始します。これにより、docker-compose.ymlからすべてのサービスが開始されます。これには以下が含まれます:
|
||||||
- searxng
|
- searxng
|
||||||
- redis (searxngに必要)
|
- redis(searxngに必要)
|
||||||
- フロントエンド
|
- frontend
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo ./start_services.sh # MacOS
|
sudo ./start_services.sh # MacOS
|
||||||
start ./start_services.cmd # Windows
|
start ./start_services.cmd # Window
|
||||||
```
|
```
|
||||||
|
|
||||||
**オプション1:** CLIインターフェースで実行。
|
**オプション1:** CLIインターフェースで実行します。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 cli.py
|
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インターフェースが表示されます。
|
`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を実行するためのセットアップ**
|
||||||
|
|
||||||
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
|
強力なコンピューターまたは使用できるサーバーがあるが、ラップトップから使用したい場合は、カスタムLLMサーバーを使用してリモートサーバーでLLMを実行するオプションがあります。
|
||||||
|
|
||||||
サービスが`./start_services.sh`で起動していることを確認し、`python3 main.py`でagenticSeekを実行します。
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sudo ./start_services.sh
|
|
||||||
python3 main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
`>>> `と表示されます
|
|
||||||
これは、agenticSeekが指示を待っていることを示します。
|
|
||||||
configで`listen = True`を設定することで、音声認識を使用することもできます。
|
|
||||||
|
|
||||||
終了するには、単に`goodbye`と言います。
|
|
||||||
|
|
||||||
以下は使用例です:
|
|
||||||
|
|
||||||
### コーディング/バッシュ
|
|
||||||
|
|
||||||
> *Pythonでスネークゲームを作成*
|
|
||||||
|
|
||||||
> *C言語で行列の掛け算を教えて*
|
|
||||||
|
|
||||||
> *Golangでブラックジャックを作成*
|
|
||||||
|
|
||||||
### ウェブ検索
|
|
||||||
|
|
||||||
> *日本の最先端のAI研究を行っているクールなテックスタートアップを見つけるためにウェブ検索を行う*
|
|
||||||
|
|
||||||
> *agenticSeekを作成したのは誰かをインターネットで見つけることができますか?*
|
|
||||||
|
|
||||||
> *オンラインの燃料計算機を使用して、ニースからミラノまでの旅行の費用を見積もることができますか?*
|
|
||||||
|
|
||||||
### ファイルシステム
|
|
||||||
|
|
||||||
> *契約書.pdfがどこにあるか見つけてくれませんか?*
|
|
||||||
|
|
||||||
> *ディスクにどれだけの空き容量があるか教えて*
|
|
||||||
|
|
||||||
> *READMEを読んでプロジェクトを/home/path/projectにインストールしてください*
|
|
||||||
|
|
||||||
### カジュアル
|
|
||||||
|
|
||||||
> *フランスのレンヌについて教えて*
|
|
||||||
|
|
||||||
> *博士号を追求すべきですか?*
|
|
||||||
|
|
||||||
> *最高のワークアウトルーチンは何ですか?*
|
|
||||||
|
|
||||||
|
|
||||||
クエリを入力すると、agenticSeekはタスクに最適なエージェントを割り当てます。
|
|
||||||
|
|
||||||
これは初期のプロトタイプであるため、エージェントルーティングシステムはクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
|
|
||||||
|
|
||||||
したがって、何を望んでいるか、AIがどのように進行するかについて非常に明確にする必要があります。たとえば、ウェブ検索を行いたい場合は、次のように言わないでください:
|
|
||||||
|
|
||||||
`一人旅に良い国を知っていますか?`
|
|
||||||
|
|
||||||
代わりに、次のように尋ねてください:
|
|
||||||
|
|
||||||
`ウェブ検索を行い、一人旅に最適な国を見つけてください`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## **ボーナス: 自分のサーバーでLLMを実行するためのセットアップ**
|
|
||||||
|
|
||||||
強力なコンピュータやサーバーを持っていて、それをラップトップから使用したい場合、リモートサーバーでLLMを実行するオプションがあります。
|
|
||||||
|
|
||||||
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
|
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP
|
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
|
```sh
|
||||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||||
cd agenticSeek/server/
|
cd agenticSeek/server/
|
||||||
```
|
```
|
||||||
|
|
||||||
サーバー固有の依存関係をインストールします:
|
サーバー固有の要件をインストールします:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip3 install -r requirements.txt
|
pip3 install -r requirements.txt
|
||||||
```
|
```
|
||||||
|
|
||||||
サーバースクリプトを実行します。
|
サーバー スクリプトを実行します。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
python3 app.py --provider ollama --port 3333
|
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アドレスに設定します。
|
`provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -313,44 +361,46 @@ provider_model = deepseek-r1:70b
|
|||||||
provider_server_address = x.x.x.x:3333
|
provider_server_address = x.x.x.x:3333
|
||||||
```
|
```
|
||||||
|
|
||||||
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
|
|
||||||
|
|
||||||
|
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 音声認識
|
## 音声認識
|
||||||
|
|
||||||
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
現在、音声認識は英語でのみ機能することに注意してください。
|
||||||
|
|
||||||
|
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
|
||||||
|
|
||||||
```
|
```
|
||||||
listen = True
|
listen = True
|
||||||
```
|
```
|
||||||
|
|
||||||
有効にすると、音声認識機能はトリガーキーワード(エージェントの名前)を待ちます。その後、入力を処理します。エージェントの名前は*config.ini*ファイルの`agent_name`値を更新することでカスタマイズできます:
|
有効にすると、音声認識機能は、入力を処理し始める前にトリガーキーワード(エージェントの名前)をリッスンします。*config.ini*ファイルで`agent_name`の値を更新することで、エージェントの名前をカスタマイズできます:
|
||||||
|
|
||||||
```
|
```
|
||||||
agent_name = Friday
|
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?"
|
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 設定
|
## 設定
|
||||||
|
|
||||||
設定例:
|
設定例:
|
||||||
```
|
```
|
||||||
[MAIN]
|
[MAIN]
|
||||||
is_local = True
|
is_local = True
|
||||||
provider_name = ollama
|
provider_name = ollama
|
||||||
provider_model = deepseek-r1:1.5b
|
provider_model = deepseek-r1:32b
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
agent_name = Friday
|
agent_name = Friday
|
||||||
recover_last_session = False
|
recover_last_session = False
|
||||||
@@ -359,7 +409,7 @@ speak = False
|
|||||||
listen = False
|
listen = False
|
||||||
work_dir = /Users/mlg/Documents/ai_folder
|
work_dir = /Users/mlg/Documents/ai_folder
|
||||||
jarvis_personality = False
|
jarvis_personality = False
|
||||||
languages = en ja
|
languages = en zh
|
||||||
[BROWSER]
|
[BROWSER]
|
||||||
headless_browser = False
|
headless_browser = False
|
||||||
stealth_mode = False
|
stealth_mode = False
|
||||||
@@ -367,112 +417,154 @@ stealth_mode = False
|
|||||||
|
|
||||||
**説明**:
|
**説明**:
|
||||||
|
|
||||||
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。
|
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)か。
|
||||||
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)。
|
|
||||||
- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。
|
- provider_name -> 使用するプロバイダー(`ollama`、`server`、`lm-studio`、`deepseek-api`のいずれか)
|
||||||
- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。
|
|
||||||
- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます。
|
- provider_model -> 使用するモデル、例:deepseek-r1:32b。
|
||||||
- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。
|
|
||||||
- save_session -> セッションデータを保存する(True)か、しない(False)。
|
- provider_server_address -> サーバーアドレス、例:ローカルの場合は127.0.0.1:11434。非ローカルAPIの場合は何でも設定します。
|
||||||
- speak -> 音声出力を有効にする(True)か、しない(False)。
|
|
||||||
- listen -> 音声入力を有効にする(True)か、しない(False)。
|
- agent_name -> エージェントの名前、例:Friday。TTSのトリガーワードとして使用されます。
|
||||||
- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。
|
|
||||||
- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)か、しない(False)。これは単にプロンプトファイルを変更するだけです。
|
- recover_last_session -> 前回のセッションから再開する(True)かしない(False)か。
|
||||||
- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。
|
|
||||||
- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
|
- save_session -> セッションデータを保存する(True)かしない(False)か。
|
||||||
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
|
|
||||||
|
- 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を簡単に実行 |
|
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
|
||||||
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 |
|
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行します |
|
||||||
| lm-studio | はい | LM studio(`lm-studio`)を使用してローカルでLLMを実行 |
|
| lm-studio | はい | LM studioでLLMをローカル実行します(`lm-studio`) |
|
||||||
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
|
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
|
||||||
| deepseek-api | いいえ | Deepseek API(非プライベート) |
|
| deepseek-api | いいえ | Deepseek API(非プライベート) |
|
||||||
| huggingface| いいえ | Hugging-Face API(非プライベート) |
|
| huggingface| いいえ | Hugging-Face API(非プライベート) |
|
||||||
| togetherAI | いいえ | together AI API(非プライベート)を使用
|
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
|
||||||
|
| google | いいえ | google gemini APIを使用(非プライベート) |
|
||||||
|
|
||||||
|
プロバイダーを選択するには、config.iniを変更します:
|
||||||
プロバイダーを選択するには、config.iniを変更します:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
is_local = False
|
is_local = True
|
||||||
provider_name = openai
|
provider_name = ollama
|
||||||
provider_model = gpt-4o
|
provider_model = deepseek-r1:32b
|
||||||
provider_server_address = 127.0.0.1:5000
|
provider_server_address = 127.0.0.1:5000
|
||||||
```
|
```
|
||||||
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalse。
|
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalseである必要があります。
|
||||||
|
|
||||||
`provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。
|
`provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。
|
||||||
|
|
||||||
`provider_model`: エージェントが使用するモデルを設定します。
|
`provider_model`: エージェントが使用するモデルを設定します。
|
||||||
|
|
||||||
`provider_server_address`: サーバープロバイダーを使用しない場合は何でも設定できます。
|
`provider_server_address`: サーバーアドレス。APIプロバイダーには使用されません。
|
||||||
|
|
||||||
# 既知の問題
|
# 既知の問題
|
||||||
|
|
||||||
## Chromedriverの問題
|
## Chromedriverの問題
|
||||||
|
|
||||||
**既知のエラー#1:** *chromedriverの不一致*
|
**既知のエラー #1:** *chromedriverの不一致*
|
||||||
|
|
||||||
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
|
`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`
|
Current browser version is 134.0.6998.89 with binary path`
|
||||||
|
|
||||||
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
|
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
|
||||||
|
|
||||||
最新バージョンをダウンロードするには、次のリンクにアクセスしてください:
|
最新バージョンをダウンロードするためにナビゲートする必要があります:
|
||||||
|
|
||||||
https://developer.chrome.com/docs/chromedriver/downloads
|
https://developer.chrome.com/docs/chromedriver/downloads
|
||||||
|
|
||||||
Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください:
|
Chromeバージョン115以降を使用している場合は、以下にアクセスしてください:
|
||||||
|
|
||||||
https://googlechromelabs.github.io/chrome-for-testing/
|
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
|
## FAQ
|
||||||
|
|
||||||
**Q: どのようなハードウェアが必要ですか?**
|
**Q: どのようなハードウェアが必要ですか?**
|
||||||
|
|
||||||
| モデルサイズ | GPU | コメント |
|
| モデルサイズ | GPU | コメント |
|
||||||
|-----------|--------|-----------------------------------------------------------|
|
|-----------|------------|--------------------------------------------------------------------------|
|
||||||
| 7B | 8GB VRAM | ⚠️ 推奨されません。パフォーマンスが低く、頻繁に幻覚を起こし、プランナーエージェントが失敗する可能性が高いです。 |
|
| 7B | 8GB VRAM | ⚠️ 非推奨。パフォーマンスが悪く、幻覚が頻繁に発生し、プランナーエージェントは失敗する可能性が高いです。 |
|
||||||
| 14B | 12GB VRAM (例: RTX 3060) | ✅ 簡単なタスクには使用可能です。ウェブブラウジングや計画タスクには苦労する可能性があります。 |
|
| 14B | 12GB VRAM(例:RTX 3060) | ✅ 簡単なタスクには使用可能。ウェブブラウジングや計画タスクで苦労する可能性があります。 |
|
||||||
| 32B | 24GB以上のVRAM (例: RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画にはまだ苦労する可能性があります。 |
|
| 32B | 24GB以上のVRAM(例:RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画でまだ苦労する可能性があります。 |
|
||||||
| 70B+ | 48GB以上のVRAM (例: Mac Studio) | 💪 優れた性能。高度なユースケースに推奨されます。 |
|
| 70B+ | 48GB以上のVRAM(例:mac studio) | 💪 素晴らしい。高度なユースケースに推奨されます。 |
|
||||||
|
|
||||||
**Q: なぜ他のモデルではなくDeepseek R1を選ぶのですか?**
|
**Q: なぜ他のモデルではなくDeepseek R1なのですか?**
|
||||||
|
|
||||||
Deepseek R1は、そのサイズに対して推論とツールの使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
|
Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに合っていると考えており、他のモデルも正常に動作しますが、Deepseekが私たちの主要な選択肢です。
|
||||||
|
|
||||||
**Q: `main.py`を実行するとエラーが発生します。どうすればよいですか?**
|
**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?**
|
||||||
|
|
||||||
Ollamaが実行中であることを確認してください(`ollama serve`)、`config.ini`がプロバイダーに一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
|
ローカルが実行されていること(`ollama serve`)、`config.ini`がプロバイダーと一致していること、依存関係がインストールされていることを確認してください。それでも解決しない場合は、遠慮なく問題を提起してください。
|
||||||
|
|
||||||
**Q: 本当に100%ローカルで実行できますか?**
|
**Q: 本当に100%ローカルで実行できますか?**
|
||||||
|
|
||||||
はい、OllamaまたはServerプロバイダーを使用すると、すべての音声認識、LLM、および音声合成モデルがローカルで実行されます。非ローカルオプション(OpenAIまたは他のAPI)はオプションです。
|
はい、Ollama、lm-studio、またはサーバープロバイダーを使用すると、すべての音声認識、LLM、テキスト読み上げモデルがローカルで実行されます。非ローカルオプション(OpenAIまたはその他のAPI)はオプションです。
|
||||||
|
|
||||||
**Q: Manusを持っているのに、なぜAgenticSeekを使用する必要があるのですか?**
|
**Q: Manusがあるのに、なぜAgenticSeekを使うべきなのですか?**
|
||||||
|
|
||||||
これは、AIエージェントに関する興味から始まったサイドプロジェクトです。特別な点は、ローカルモデルを使用し、APIを避けることです。
|
これは、AIエージェントへの関心から始めたサイドプロジェクトです。特別なのは、ローカルモデルを使用し、APIを避けたいということです。
|
||||||
私たちは、JarvisやFriday(アイアンマン映画)からインスピレーションを得て、「クール」にしようとしましたが、機能性に関してはManusから多くのインスピレーションを得ています。なぜなら、人々が最初に求めているのはローカルのManusの代替品だからです。
|
私たちはJarvisとFriday(アイアンマン映画)からインスピレーションを得て「クール」にしましたが、機能性についてはManusからより多くのインスピレーションを得ています。なぜなら、それが人々が最初に望むもの、つまりローカルなManusの代替だからです。
|
||||||
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIのコストを回避します。
|
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIコストを回避します。
|
||||||
|
|
||||||
## 貢献
|
## 貢献する
|
||||||
|
|
||||||
AgenticSeekを改善するための開発者を探しています!オープンな問題やディスカッションを確認してください。
|
AgenticSeekを改善するための開発者を募集しています!オープンな問題やディスカッションを確認してください。
|
||||||
|
|
||||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
[貢献ガイド](./docs/CONTRIBUTING.md)
|
||||||
|
|
||||||
## 著者:
|
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||||
> [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) | 台北時間 |(常に忙しい)
|
||||||
@@ -22,6 +22,10 @@ from sources.utility import pretty_print
|
|||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
from sources.schemas import QueryRequest, QueryResponse
|
from sources.schemas import QueryRequest, QueryResponse
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
from celery import Celery
|
from celery import Celery
|
||||||
|
|
||||||
@@ -58,7 +62,7 @@ def initialize_system():
|
|||||||
logger.info(f"Provider initialized: {provider.provider_name} ({provider.model})")
|
logger.info(f"Provider initialized: {provider.provider_name} ({provider.model})")
|
||||||
|
|
||||||
browser = Browser(
|
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
|
anticaptcha_manual_install=stealth_mode
|
||||||
)
|
)
|
||||||
logger.info("Browser initialized")
|
logger.info("Browser initialized")
|
||||||
@@ -128,6 +132,12 @@ async def is_active():
|
|||||||
logger.info("Is active endpoint called")
|
logger.info("Is active endpoint called")
|
||||||
return {"is_active": interaction.is_active}
|
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")
|
@api.get("/latest_answer")
|
||||||
async def get_latest_answer():
|
async def get_latest_answer():
|
||||||
global query_resp_history
|
global query_resp_history
|
||||||
@@ -138,6 +148,7 @@ async def get_latest_answer():
|
|||||||
query_resp = {
|
query_resp = {
|
||||||
"done": "false",
|
"done": "false",
|
||||||
"answer": interaction.current_agent.last_answer,
|
"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",
|
"agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None",
|
||||||
"success": interaction.current_agent.success,
|
"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 {},
|
"blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {},
|
||||||
@@ -145,27 +156,31 @@ async def get_latest_answer():
|
|||||||
"uid": uid
|
"uid": uid
|
||||||
}
|
}
|
||||||
interaction.current_agent.last_answer = ""
|
interaction.current_agent.last_answer = ""
|
||||||
|
interaction.current_agent.last_reasoning = ""
|
||||||
query_resp_history.append(query_resp)
|
query_resp_history.append(query_resp)
|
||||||
return JSONResponse(status_code=200, content=query_resp)
|
return JSONResponse(status_code=200, content=query_resp)
|
||||||
if query_resp_history:
|
if query_resp_history:
|
||||||
return JSONResponse(status_code=200, content=query_resp_history[-1])
|
return JSONResponse(status_code=200, content=query_resp_history[-1])
|
||||||
return JSONResponse(status_code=404, content={"error": "No answer available"})
|
return JSONResponse(status_code=404, content={"error": "No answer available"})
|
||||||
|
|
||||||
async def think_wrapper(interaction, query, tts_enabled):
|
async def think_wrapper(interaction, query):
|
||||||
try:
|
try:
|
||||||
interaction.tts_enabled = tts_enabled
|
|
||||||
interaction.last_query = query
|
interaction.last_query = query
|
||||||
logger.info("Agents request is being processed")
|
logger.info("Agents request is being processed")
|
||||||
success = await interaction.think()
|
success = await interaction.think()
|
||||||
if not success:
|
if not success:
|
||||||
interaction.last_answer = "Error: No answer from agent"
|
interaction.last_answer = "Error: No answer from agent"
|
||||||
|
interaction.last_reasoning = "Error: No reasoning from agent"
|
||||||
interaction.last_success = False
|
interaction.last_success = False
|
||||||
else:
|
else:
|
||||||
interaction.last_success = True
|
interaction.last_success = True
|
||||||
|
pretty_print(interaction.last_answer)
|
||||||
|
interaction.speak_answer()
|
||||||
return success
|
return success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in think_wrapper: {str(e)}")
|
logger.error(f"Error in think_wrapper: {str(e)}")
|
||||||
interaction.last_answer = f"Error: {str(e)}"
|
interaction.last_answer = f""
|
||||||
|
interaction.last_reasoning = f"Error: {str(e)}"
|
||||||
interaction.last_success = False
|
interaction.last_success = False
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
@@ -176,6 +191,7 @@ async def process_query(request: QueryRequest):
|
|||||||
query_resp = QueryResponse(
|
query_resp = QueryResponse(
|
||||||
done="false",
|
done="false",
|
||||||
answer="",
|
answer="",
|
||||||
|
reasoning="",
|
||||||
agent_name="Unknown",
|
agent_name="Unknown",
|
||||||
success="false",
|
success="false",
|
||||||
blocks={},
|
blocks={},
|
||||||
@@ -188,11 +204,12 @@ async def process_query(request: QueryRequest):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
is_generating = True
|
is_generating = True
|
||||||
success = await think_wrapper(interaction, request.query, request.tts_enabled)
|
success = await think_wrapper(interaction, request.query)
|
||||||
is_generating = False
|
is_generating = False
|
||||||
|
|
||||||
if not success:
|
if not success:
|
||||||
query_resp.answer = interaction.last_answer
|
query_resp.answer = interaction.last_answer
|
||||||
|
query_resp.reasoning = interaction.last_reasoning
|
||||||
return JSONResponse(status_code=400, content=query_resp.jsonify())
|
return JSONResponse(status_code=400, content=query_resp.jsonify())
|
||||||
|
|
||||||
if interaction.current_agent:
|
if interaction.current_agent:
|
||||||
@@ -207,11 +224,11 @@ async def process_query(request: QueryRequest):
|
|||||||
logger.info(f"Blocks: {blocks_json}")
|
logger.info(f"Blocks: {blocks_json}")
|
||||||
query_resp.done = "true"
|
query_resp.done = "true"
|
||||||
query_resp.answer = interaction.last_answer
|
query_resp.answer = interaction.last_answer
|
||||||
|
query_resp.reasoning = interaction.last_reasoning
|
||||||
query_resp.agent_name = interaction.current_agent.agent_name
|
query_resp.agent_name = interaction.current_agent.agent_name
|
||||||
query_resp.success = str(interaction.last_success)
|
query_resp.success = str(interaction.last_success)
|
||||||
query_resp.blocks = blocks_json
|
query_resp.blocks = blocks_json
|
||||||
|
|
||||||
# Store the raw dictionary representation
|
|
||||||
query_resp_dict = {
|
query_resp_dict = {
|
||||||
"done": query_resp.done,
|
"done": query_resp.done,
|
||||||
"answer": query_resp.answer,
|
"answer": query_resp.answer,
|
||||||
@@ -234,4 +251,9 @@ async def process_query(request: QueryRequest):
|
|||||||
interaction.save_session()
|
interaction.save_session()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
envport = os.getenv("BACKEND_PORT")
|
||||||
|
if envport:
|
||||||
|
port = int(envport)
|
||||||
|
else:
|
||||||
|
port = 8000
|
||||||
uvicorn.run(api, host="0.0.0.0", port=8000)
|
uvicorn.run(api, host="0.0.0.0", port=8000)
|
||||||
@@ -7,7 +7,7 @@ import asyncio
|
|||||||
|
|
||||||
from sources.llm_provider import Provider
|
from sources.llm_provider import Provider
|
||||||
from sources.interaction import Interaction
|
from sources.interaction import Interaction
|
||||||
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent
|
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent
|
||||||
from sources.browser import Browser, create_driver
|
from sources.browser import Browser, create_driver
|
||||||
from sources.utility import pretty_print
|
from sources.utility import pretty_print
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ async def main():
|
|||||||
is_local=config.getboolean('MAIN', 'is_local'))
|
is_local=config.getboolean('MAIN', 'is_local'))
|
||||||
|
|
||||||
browser = Browser(
|
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
|
anticaptcha_manual_install=stealth_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +48,10 @@ async def main():
|
|||||||
provider=provider, verbose=False, browser=browser),
|
provider=provider, verbose=False, browser=browser),
|
||||||
PlannerAgent(name="Planner",
|
PlannerAgent(name="Planner",
|
||||||
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
|
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
|
||||||
provider=provider, verbose=False, browser=browser)
|
provider=provider, verbose=False, browser=browser),
|
||||||
|
#McpAgent(name="MCP Agent",
|
||||||
|
# prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
|
||||||
|
# provider=provider, verbose=False), # NOTE under development
|
||||||
]
|
]
|
||||||
|
|
||||||
interaction = Interaction(agents,
|
interaction = Interaction(agents,
|
||||||
@@ -62,6 +65,7 @@ async def main():
|
|||||||
interaction.get_user()
|
interaction.get_user()
|
||||||
if await interaction.think():
|
if await interaction.think():
|
||||||
interaction.show_answer()
|
interaction.show_answer()
|
||||||
|
interaction.speak_answer()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if config.getboolean('MAIN', 'save_session'):
|
if config.getboolean('MAIN', 'save_session'):
|
||||||
interaction.save_session()
|
interaction.save_session()
|
||||||
|
|||||||
+4
-4
@@ -3,14 +3,14 @@ is_local = True
|
|||||||
provider_name = ollama
|
provider_name = ollama
|
||||||
provider_model = deepseek-r1:14b
|
provider_model = deepseek-r1:14b
|
||||||
provider_server_address = 127.0.0.1:11434
|
provider_server_address = 127.0.0.1:11434
|
||||||
agent_name = Friday
|
agent_name = Name_of_your_AI
|
||||||
recover_last_session = False
|
recover_last_session = False
|
||||||
save_session = False
|
save_session = False
|
||||||
speak = False
|
speak = False
|
||||||
listen = False
|
listen = False
|
||||||
work_dir = /Users/mlg/Documents/ai_folder
|
work_dir = /Users/mlg/Documents/workspace_for_agenticseek
|
||||||
jarvis_personality = False
|
jarvis_personality = False
|
||||||
languages = en
|
languages = en
|
||||||
[BROWSER]
|
[BROWSER]
|
||||||
headless_browser = False
|
headless_browser = True
|
||||||
stealth_mode = True
|
stealth_mode = False
|
||||||
+34
-32
@@ -3,6 +3,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
redis:
|
redis:
|
||||||
container_name: redis
|
container_name: redis
|
||||||
|
profiles: ["core", "full"]
|
||||||
image: docker.io/valkey/valkey:8-alpine
|
image: docker.io/valkey/valkey:8-alpine
|
||||||
command: valkey-server --save 30 1 --loglevel warning
|
command: valkey-server --save 30 1 --loglevel warning
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -24,6 +25,7 @@ services:
|
|||||||
|
|
||||||
searxng:
|
searxng:
|
||||||
container_name: searxng
|
container_name: searxng
|
||||||
|
profiles: ["core", "full"]
|
||||||
image: docker.io/searxng/searxng:latest
|
image: docker.io/searxng/searxng:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
@@ -31,8 +33,8 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./searxng:/etc/searxng:rw
|
- ./searxng:/etc/searxng:rw
|
||||||
environment:
|
environment:
|
||||||
- SEARXNG_BASE_URL=http://localhost:8080/
|
- SEARXNG_BASE_URL=${SEARXNG_BASE_URL:-http://localhost:8080/}
|
||||||
- SEARXNG_SECRET_KEY=$(openssl rand -hex 32)
|
- SEARXNG_SECRET_KEY=${SEARXNG_SECRET_KEY}
|
||||||
- UWSGI_WORKERS=4
|
- UWSGI_WORKERS=4
|
||||||
- UWSGI_THREADS=4
|
- UWSGI_THREADS=4
|
||||||
cap_add:
|
cap_add:
|
||||||
@@ -51,6 +53,7 @@ services:
|
|||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
container_name: frontend
|
container_name: frontend
|
||||||
|
profiles: ["core", "full"]
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: ./frontend
|
||||||
dockerfile: Dockerfile.frontend
|
dockerfile: Dockerfile.frontend
|
||||||
@@ -62,39 +65,38 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
- CHOKIDAR_USEPOLLING=true
|
- CHOKIDAR_USEPOLLING=true
|
||||||
- BACKEND_URL=http://backend:8000
|
- REACT_APP_BACKEND_URL=http://0.0.0.0:${BACKEND_PORT:-8000}
|
||||||
networks:
|
networks:
|
||||||
- agentic-seek-net
|
- agentic-seek-net
|
||||||
|
|
||||||
# NOTE: backend service is not working yet due to issue with chromedriver on docker.
|
backend:
|
||||||
# Therefore backend is run on host machine.
|
container_name: backend
|
||||||
# Open to pull requests to fix this.
|
profiles: ["backend", "full"]
|
||||||
|
build:
|
||||||
#backend:
|
context: .
|
||||||
# container_name: backend
|
dockerfile: Dockerfile.backend
|
||||||
# build:
|
ports:
|
||||||
# context: ./
|
- ${BACKEND_PORT:-7777}:${BACKEND_PORT:-7777}
|
||||||
# dockerfile: Dockerfile.backend
|
- ${OLLAMA_PORT:-11434}:${OLLAMA_PORT:-11434}
|
||||||
# stdin_open: true
|
- ${LM_STUDIO_PORT:-1234}:${LM_STUDIO_PORT:-1234}
|
||||||
# tty: true
|
- ${CUSTOM_ADDITIONAL_LLM_PORT:-8000}:${CUSTOM_ADDITIONAL_LLM_PORT:-8000}
|
||||||
# shm_size: 8g
|
volumes:
|
||||||
# ports:
|
- ./:/app
|
||||||
# - "8000:8000"
|
- ${WORK_DIR:-.}:/opt/workspace
|
||||||
# volumes:
|
command: python3 api.py
|
||||||
# - ./:/app
|
environment:
|
||||||
# environment:
|
- SEARXNG_URL=${SEARXNG_BASE_URL:-http://searxng:8080}
|
||||||
# - NODE_ENV=development
|
- REDIS_URL=${REDIS_BASE_URL:-redis://redis:6379/0}
|
||||||
# - REDIS_URL=redis://redis:6379/0
|
- WORK_DIR=/opt/workspace
|
||||||
# - SEARXNG_URL=http://searxng:8080
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
# - OLLAMA_URL=http://localhost:11434
|
- DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
|
||||||
# - LM_STUDIO_URL=http://localhost:1234
|
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
|
||||||
# extra_hosts:
|
- TOGETHER_API_KEY=${TOGETHER_API_KEY}
|
||||||
# - "host.docker.internal:host-gateway"
|
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
|
||||||
# depends_on:
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
||||||
# - redis
|
- HUGGINGFACE_API_KEY=${HUGGINGFACE_API_KEY}
|
||||||
# - searxng
|
- DSK_DEEPSEEK_API_KEY=${DSK_DEEPSEEK_API_KEY}
|
||||||
# networks:
|
network_mode: "host"
|
||||||
# - agentic-seek-net
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
redis-data:
|
redis-data:
|
||||||
|
|||||||
+29
-25
@@ -6,8 +6,8 @@ We as members, contributors, and leaders pledge to make participation in our
|
|||||||
community a harassment-free experience for everyone, regardless of age, body
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||||
and orientation.
|
identity and orientation.
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||||
diverse, inclusive, and healthy community.
|
diverse, inclusive, and healthy community.
|
||||||
@@ -22,17 +22,17 @@ community include:
|
|||||||
* Giving and gracefully accepting constructive feedback
|
* Giving and gracefully accepting constructive feedback
|
||||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||||
and learning from the experience
|
and learning from the experience
|
||||||
* Focusing on what is best not just for us as individuals, but for the
|
* Focusing on what is best not just for us as individuals, but for the overall
|
||||||
overall community
|
community
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
* The use of sexualized language or imagery, and sexual attention or
|
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||||
advances of any kind
|
any kind
|
||||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
* Public or private harassment
|
* Public or private harassment
|
||||||
* Publishing others' private information, such as a physical or email
|
* Publishing others' private information, such as a physical or email address,
|
||||||
address, without their explicit permission
|
without their explicit permission
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
* Other conduct which could reasonably be considered inappropriate in a
|
||||||
professional setting
|
professional setting
|
||||||
|
|
||||||
@@ -52,15 +52,15 @@ decisions when appropriate.
|
|||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
This Code of Conduct applies within all community spaces, and also applies when
|
||||||
an individual is officially representing the community in public spaces.
|
an individual is officially representing the community in public spaces.
|
||||||
Examples of representing our community include using an official e-mail address,
|
Examples of representing our community include using an official email address,
|
||||||
posting via an official social media account, or acting as an appointed
|
posting via an official social media account, or acting as an appointed
|
||||||
representative at an online or offline event.
|
representative at an online or offline event.
|
||||||
|
|
||||||
## Enforcement
|
## Enforcement
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
reported to the community leaders responsible for enforcement at
|
reported to the community leaders responsible for enforcement:
|
||||||
.
|
you need to send a private message to `fossowl` or `mow8758` on discord.
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
All complaints will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
All community leaders are obligated to respect the privacy and security of the
|
||||||
@@ -82,15 +82,15 @@ behavior was inappropriate. A public apology may be requested.
|
|||||||
|
|
||||||
### 2. Warning
|
### 2. Warning
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
**Community Impact**: A violation through a single incident or series of
|
||||||
of actions.
|
actions.
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
**Consequence**: A warning with consequences for continued behavior. No
|
||||||
interaction with the people involved, including unsolicited interaction with
|
interaction with the people involved, including unsolicited interaction with
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
those enforcing the Code of Conduct, for a specified period of time. This
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
includes avoiding interactions in community spaces as well as external channels
|
||||||
like social media. Violating these terms may lead to a temporary or
|
like social media. Violating these terms may lead to a temporary or permanent
|
||||||
permanent ban.
|
ban.
|
||||||
|
|
||||||
### 3. Temporary Ban
|
### 3. Temporary Ban
|
||||||
|
|
||||||
@@ -109,20 +109,24 @@ Violating these terms may lead to a permanent ban.
|
|||||||
standards, including sustained inappropriate behavior, harassment of an
|
standards, including sustained inappropriate behavior, harassment of an
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
individual, or aggression toward or disparagement of classes of individuals.
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||||
the community.
|
community.
|
||||||
|
|
||||||
## Attribution
|
## Attribution
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||||
version 2.0, available at
|
version 2.1, available at
|
||||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
Community Impact Guidelines were inspired by
|
||||||
enforcement ladder](https://github.com/mozilla/diversity).
|
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
For answers to common questions about this code of conduct, see the FAQ at
|
||||||
https://www.contributor-covenant.org/faq. Translations are available at
|
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||||
https://www.contributor-covenant.org/translations.
|
[https://www.contributor-covenant.org/translations][translations].
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||||
|
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||||
|
[FAQ]: https://www.contributor-covenant.org/faq
|
||||||
|
[translations]: https://www.contributor-covenant.org/translations
|
||||||
|
|||||||
@@ -2,11 +2,11 @@
|
|||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- Python 3.8 or higher.
|
- Python 3.10 or higher.
|
||||||
- Docker or Orbstack.
|
- Docker or Orbstack or Podman.
|
||||||
- Ollama with some deepseek-r1 variant installed or similar local reasoning model.
|
- Ollama with some deepseek-r1 variant installed or similar local reasoning model.
|
||||||
- Basic familiarity with Python and AI models.
|
- Basic familiarity with Python and AI models.
|
||||||
- Join the discord (optional): https://discord.gg/v8r4xy8x
|
- Join the discord (optional): https://discord.gg/8hGDaME3TC
|
||||||
|
|
||||||
## Contribution Guidelines
|
## Contribution Guidelines
|
||||||
|
|
||||||
|
|||||||
+1177
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
|||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
"web-vitals": "^2.1.4"
|
"web-vitals": "^2.1.4"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -167,6 +167,88 @@ body {
|
|||||||
margin-bottom: 8px;
|
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 {
|
.placeholder {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #64748b; /* lighter gray */
|
color: #64748b; /* lighter gray */
|
||||||
@@ -366,6 +448,10 @@ body {
|
|||||||
color: #28a745; /* success */
|
color: #28a745; /* success */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-failure {
|
||||||
|
color: #d21b0b; /* success */
|
||||||
|
}
|
||||||
|
|
||||||
.block pre {
|
.block pre {
|
||||||
background-color: #1a202c; /* Darker than darkCard */
|
background-color: #1a202c; /* Darker than darkCard */
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
import { colors } from './colors';
|
import { colors } from './colors';
|
||||||
|
|
||||||
|
const BACKEND_URL = process.env.BACKEND_PORT || 'http://0.0.0.0:8000';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [query, setQuery] = useState('');
|
const [query, setQuery] = useState('');
|
||||||
const [messages, setMessages] = useState([]);
|
const [messages, setMessages] = useState([]);
|
||||||
@@ -12,6 +15,7 @@ function App() {
|
|||||||
const [responseData, setResponseData] = useState(null);
|
const [responseData, setResponseData] = useState(null);
|
||||||
const [isOnline, setIsOnline] = useState(false);
|
const [isOnline, setIsOnline] = useState(false);
|
||||||
const [status, setStatus] = useState('Agents ready');
|
const [status, setStatus] = useState('Agents ready');
|
||||||
|
const [expandedReasoning, setExpandedReasoning] = useState(new Set());
|
||||||
const messagesEndRef = useRef(null);
|
const messagesEndRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -25,7 +29,7 @@ function App() {
|
|||||||
|
|
||||||
const checkHealth = async () => {
|
const checkHealth = async () => {
|
||||||
try {
|
try {
|
||||||
await axios.get('http://0.0.0.0:8000/health');
|
await axios.get(`${BACKEND_URL}/health`);
|
||||||
setIsOnline(true);
|
setIsOnline(true);
|
||||||
console.log('System is online');
|
console.log('System is online');
|
||||||
} catch {
|
} catch {
|
||||||
@@ -37,7 +41,7 @@ function App() {
|
|||||||
const fetchScreenshot = async () => {
|
const fetchScreenshot = async () => {
|
||||||
try {
|
try {
|
||||||
const timestamp = new Date().getTime();
|
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(`${BACKEND_URL}/screenshots/updated_screen.png?timestamp=${timestamp}`, {
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
});
|
});
|
||||||
console.log('Screenshot fetched successfully');
|
console.log('Screenshot fetched successfully');
|
||||||
@@ -74,9 +78,21 @@ function App() {
|
|||||||
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
|
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 () => {
|
const fetchLatestAnswer = async () => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get('http://0.0.0.0:8000/latest_answer');
|
const res = await axios.get(`${BACKEND_URL}/latest_answer`);
|
||||||
const data = res.data;
|
const data = res.data;
|
||||||
|
|
||||||
updateData(data);
|
updateData(data);
|
||||||
@@ -93,6 +109,7 @@ function App() {
|
|||||||
{
|
{
|
||||||
type: 'agent',
|
type: 'agent',
|
||||||
content: data.answer,
|
content: data.answer,
|
||||||
|
reasoning: data.reasoning,
|
||||||
agentName: data.agent_name,
|
agentName: data.agent_name,
|
||||||
status: data.status,
|
status: data.status,
|
||||||
uid: data.uid,
|
uid: data.uid,
|
||||||
@@ -120,6 +137,19 @@ function App() {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleStop = async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
checkHealth();
|
||||||
|
setIsLoading(false);
|
||||||
|
setError(null);
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${BACKEND_URL}/stop`);
|
||||||
|
setStatus("Requesting stop...");
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error stopping the agent:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
checkHealth();
|
checkHealth();
|
||||||
@@ -134,7 +164,7 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
console.log('Sending query:', query);
|
console.log('Sending query:', query);
|
||||||
setQuery('waiting for response...');
|
setQuery('waiting for response...');
|
||||||
const res = await axios.post('http://0.0.0.0:8000/query', {
|
const res = await axios.post(`${BACKEND_URL}/query`, {
|
||||||
query,
|
query,
|
||||||
tts_enabled: false
|
tts_enabled: false
|
||||||
});
|
});
|
||||||
@@ -171,8 +201,6 @@ function App() {
|
|||||||
</header>
|
</header>
|
||||||
<main className="main">
|
<main className="main">
|
||||||
<div className="app-sections">
|
<div className="app-sections">
|
||||||
|
|
||||||
|
|
||||||
<div className="chat-section">
|
<div className="chat-section">
|
||||||
<h2>Chat Interface</h2>
|
<h2>Chat Interface</h2>
|
||||||
<div className="messages">
|
<div className="messages">
|
||||||
@@ -190,10 +218,28 @@ function App() {
|
|||||||
: 'error-message'
|
: 'error-message'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
<div className="message-header">
|
||||||
{msg.type === 'agent' && (
|
{msg.type === 'agent' && (
|
||||||
<span className="agent-name">{msg.agentName}</span>
|
<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>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -212,6 +258,9 @@ function App() {
|
|||||||
<button type="submit" disabled={isLoading}>
|
<button type="submit" disabled={isLoading}>
|
||||||
Send
|
Send
|
||||||
</button>
|
</button>
|
||||||
|
<button onClick={handleStop}>
|
||||||
|
Stop
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -241,9 +290,11 @@ function App() {
|
|||||||
<p className="block-tool">Tool: {block.tool_type}</p>
|
<p className="block-tool">Tool: {block.tool_type}</p>
|
||||||
<pre>{block.block}</pre>
|
<pre>{block.block}</pre>
|
||||||
<p className="block-feedback">Feedback: {block.feedback}</p>
|
<p className="block-feedback">Feedback: {block.feedback}</p>
|
||||||
<p className="block-success">
|
{block.success ? (
|
||||||
Success: {block.success ? 'Yes' : 'No'}
|
<p className="block-success">Success</p>
|
||||||
</p>
|
) : (
|
||||||
|
<p className="block-failure">Failure</p>
|
||||||
|
)}
|
||||||
</div>
|
</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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
+5
-6
@@ -1,3 +1,5 @@
|
|||||||
|
kokoro==0.9.4
|
||||||
|
certifi==2025.4.26
|
||||||
fastapi>=0.115.12
|
fastapi>=0.115.12
|
||||||
flask>=3.1.0
|
flask>=3.1.0
|
||||||
celery>=5.5.1
|
celery>=5.5.1
|
||||||
@@ -15,13 +17,12 @@ playsound>=1.3.0
|
|||||||
soundfile>=0.13.1
|
soundfile>=0.13.1
|
||||||
transformers>=4.46.3
|
transformers>=4.46.3
|
||||||
torch>=2.4.1
|
torch>=2.4.1
|
||||||
python-dotenv>=1.0.0
|
|
||||||
ollama>=0.4.7
|
ollama>=0.4.7
|
||||||
scipy>=1.9.3
|
scipy>=1.9.3
|
||||||
kokoro>=0.7.12
|
|
||||||
soundfile>=0.13.1
|
soundfile>=0.13.1
|
||||||
protobuf>=3.20.3
|
protobuf>=3.20.3
|
||||||
termcolor>=2.4.0
|
termcolor>=2.4.0
|
||||||
|
pypdf>=5.4.0
|
||||||
ipython>=8.13.0
|
ipython>=8.13.0
|
||||||
pyaudio>=0.2.14
|
pyaudio>=0.2.14
|
||||||
librosa>=0.10.2.post1
|
librosa>=0.10.2.post1
|
||||||
@@ -39,11 +40,9 @@ fake_useragent>=2.1.0
|
|||||||
selenium_stealth>=1.0.6
|
selenium_stealth>=1.0.6
|
||||||
undetected-chromedriver>=3.5.5
|
undetected-chromedriver>=3.5.5
|
||||||
sentencepiece>=0.2.0
|
sentencepiece>=0.2.0
|
||||||
|
together>=1.5.0
|
||||||
|
tqdm>4
|
||||||
openai
|
openai
|
||||||
sniffio
|
sniffio
|
||||||
tqdm>4
|
|
||||||
# if use chinese
|
|
||||||
ordered_set
|
ordered_set
|
||||||
pypinyin
|
pypinyin
|
||||||
cn2an
|
|
||||||
jieba
|
|
||||||
|
|||||||
@@ -3,10 +3,22 @@
|
|||||||
echo "Starting installation for Linux..."
|
echo "Starting installation for Linux..."
|
||||||
|
|
||||||
set -e
|
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
|
# Update package list
|
||||||
sudo apt-get update || { echo "Failed to update package list"; exit 1; }
|
sudo apt-get update || { echo "Failed to update package list"; exit 1; }
|
||||||
# make sure essential tool are installed
|
# make sure essential tool are installed
|
||||||
# Install essential tools
|
|
||||||
sudo apt-get install -y \
|
sudo apt-get install -y \
|
||||||
python3-dev \
|
python3-dev \
|
||||||
python3-pip \
|
python3-pip \
|
||||||
@@ -21,15 +33,15 @@ sudo apt-get install -y \
|
|||||||
libnss3 \
|
libnss3 \
|
||||||
libxss1 || { echo "Failed to install packages"; exit 1; }
|
libxss1 || { echo "Failed to install packages"; exit 1; }
|
||||||
|
|
||||||
# upgrade pip
|
# Upgrade pip for Python 3.10
|
||||||
pip install --upgrade pip
|
python3.10 -m pip install --upgrade pip || { echo "Failed to upgrade pip"; exit 1; }
|
||||||
# install wheel
|
# Install and upgrade setuptools and wheel
|
||||||
pip install --upgrade pip setuptools 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
|
# install docker compose
|
||||||
sudo apt install -y 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!"
|
echo "Installation complete for Linux!"
|
||||||
@@ -4,6 +4,18 @@ echo "Starting installation for macOS..."
|
|||||||
|
|
||||||
set -e
|
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
|
# Check if homebrew is installed
|
||||||
if ! command -v brew &> /dev/null; then
|
if ! command -v brew &> /dev/null; then
|
||||||
echo "Homebrew not found. Installing Homebrew..."
|
echo "Homebrew not found. Installing Homebrew..."
|
||||||
@@ -18,13 +30,14 @@ brew install wget
|
|||||||
brew install --cask chromedriver
|
brew install --cask chromedriver
|
||||||
# Install portaudio for pyAudio using Homebrew
|
# Install portaudio for pyAudio using Homebrew
|
||||||
brew install portaudio
|
brew install portaudio
|
||||||
# update pip
|
|
||||||
python3 -m pip install --upgrade pip
|
# Upgrade pip for Python 3.10
|
||||||
# upgrade setuptools and wheel
|
python3.10 -m pip install --upgrade pip || { echo "Failed to upgrade pip"; exit 1; }
|
||||||
pip3 install --upgrade setuptools wheel
|
# Install and upgrade setuptools and wheel
|
||||||
# Install Selenium
|
python3.10 -m pip install --upgrade setuptools wheel || { echo "Failed to install setuptools and wheel"; exit 1; }
|
||||||
pip3 install selenium
|
# Install Selenium for chromedriver
|
||||||
|
python3.10 -m pip install selenium || { echo "Failed to install selenium"; exit 1; }
|
||||||
# Install Python dependencies from requirements.txt
|
# 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!"
|
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 .file_agent import FileAgent
|
||||||
from .planner_agent import PlannerAgent
|
from .planner_agent import PlannerAgent
|
||||||
from .browser_agent import BrowserAgent
|
from .browser_agent import BrowserAgent
|
||||||
|
from .mcp_agent import McpAgent
|
||||||
|
|
||||||
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent"]
|
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent", "McpAgent"]
|
||||||
|
|||||||
+36
-5
@@ -39,14 +39,14 @@ class Agent():
|
|||||||
self.type = None
|
self.type = None
|
||||||
self.current_directory = os.getcwd()
|
self.current_directory = os.getcwd()
|
||||||
self.llm = provider
|
self.llm = provider
|
||||||
self.memory = Memory(self.load_prompt(prompt_path),
|
self.memory = None
|
||||||
recover_last_session=False, # session recovery in handled by the interaction class
|
|
||||||
memory_compression=False)
|
|
||||||
self.tools = {}
|
self.tools = {}
|
||||||
self.blocks_result = []
|
self.blocks_result = []
|
||||||
self.success = True
|
self.success = True
|
||||||
self.last_answer = ""
|
self.last_answer = ""
|
||||||
|
self.last_reasoning = ""
|
||||||
self.status_message = "Haven't started yet"
|
self.status_message = "Haven't started yet"
|
||||||
|
self.stop = False
|
||||||
self.verbose = verbose
|
self.verbose = verbose
|
||||||
self.executor = ThreadPoolExecutor(max_workers=1)
|
self.executor = ThreadPoolExecutor(max_workers=1)
|
||||||
|
|
||||||
@@ -66,6 +66,10 @@ class Agent():
|
|||||||
def get_last_answer(self) -> str:
|
def get_last_answer(self) -> str:
|
||||||
return self.last_answer
|
return self.last_answer
|
||||||
|
|
||||||
|
@property
|
||||||
|
def get_last_reasoning(self) -> str:
|
||||||
|
return self.last_reasoning
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def get_blocks(self) -> list:
|
def get_blocks(self) -> list:
|
||||||
return self.blocks_result
|
return self.blocks_result
|
||||||
@@ -90,6 +94,21 @@ class Agent():
|
|||||||
raise TypeError("Tool must be a callable object (a method)")
|
raise TypeError("Tool must be a callable object (a method)")
|
||||||
self.tools[name] = tool
|
self.tools[name] = tool
|
||||||
|
|
||||||
|
def get_tools_name(self) -> list:
|
||||||
|
"""
|
||||||
|
Get the list of tools names.
|
||||||
|
"""
|
||||||
|
return list(self.tools.keys())
|
||||||
|
|
||||||
|
def get_tools_description(self) -> str:
|
||||||
|
"""
|
||||||
|
Get the list of tools names and their description.
|
||||||
|
"""
|
||||||
|
description = ""
|
||||||
|
for name in self.get_tools_name():
|
||||||
|
description += f"{name}: {self.tools[name].description}\n"
|
||||||
|
return description
|
||||||
|
|
||||||
def load_prompt(self, file_path: str) -> str:
|
def load_prompt(self, file_path: str) -> str:
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding="utf-8") as f:
|
with open(file_path, 'r', encoding="utf-8") as f:
|
||||||
@@ -101,6 +120,13 @@ class Agent():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
def request_stop(self) -> None:
|
||||||
|
"""
|
||||||
|
Request the agent to stop.
|
||||||
|
"""
|
||||||
|
self.stop = True
|
||||||
|
self.status_message = "Stopped"
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def process(self, prompt, speech_module) -> str:
|
def process(self, prompt, speech_module) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -114,8 +140,10 @@ class Agent():
|
|||||||
Remove the reasoning block of reasoning model like deepseek.
|
Remove the reasoning block of reasoning model like deepseek.
|
||||||
"""
|
"""
|
||||||
end_tag = "</think>"
|
end_tag = "</think>"
|
||||||
end_idx = text.rfind(end_tag)+8
|
end_idx = text.rfind(end_tag)
|
||||||
return text[end_idx:]
|
if end_idx == -1:
|
||||||
|
return text
|
||||||
|
return text[end_idx+8:]
|
||||||
|
|
||||||
def extract_reasoning_text(self, text: str) -> None:
|
def extract_reasoning_text(self, text: str) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -123,6 +151,8 @@ class Agent():
|
|||||||
"""
|
"""
|
||||||
start_tag = "<think>"
|
start_tag = "<think>"
|
||||||
end_tag = "</think>"
|
end_tag = "</think>"
|
||||||
|
if text is None:
|
||||||
|
return None
|
||||||
start_idx = text.find(start_tag)
|
start_idx = text.find(start_tag)
|
||||||
end_idx = text.rfind(end_tag)+8
|
end_idx = text.rfind(end_tag)+8
|
||||||
return text[start_idx:end_idx]
|
return text[start_idx:end_idx]
|
||||||
@@ -238,6 +268,7 @@ class Agent():
|
|||||||
blocks, save_path = tool.load_exec_block(answer)
|
blocks, save_path = tool.load_exec_block(answer)
|
||||||
|
|
||||||
if blocks != None:
|
if blocks != None:
|
||||||
|
pretty_print(f"Executing {len(blocks)} {name} blocks...", color="status")
|
||||||
for block in blocks:
|
for block in blocks:
|
||||||
self.show_block(block)
|
self.show_block(block)
|
||||||
output = tool.execute([block])
|
output = tool.execute([block])
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from sources.agents.agent import Agent
|
|||||||
from sources.tools.searxSearch import searxSearch
|
from sources.tools.searxSearch import searxSearch
|
||||||
from sources.browser import Browser
|
from sources.browser import Browser
|
||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
|
from sources.memory import Memory
|
||||||
|
|
||||||
class Action(Enum):
|
class Action(Enum):
|
||||||
REQUEST_EXIT = "REQUEST_EXIT"
|
REQUEST_EXIT = "REQUEST_EXIT"
|
||||||
@@ -37,6 +38,10 @@ class BrowserAgent(Agent):
|
|||||||
self.notes = []
|
self.notes = []
|
||||||
self.date = self.get_today_date()
|
self.date = self.get_today_date()
|
||||||
self.logger = Logger("browser_agent.log")
|
self.logger = Logger("browser_agent.log")
|
||||||
|
self.memory = Memory(self.load_prompt(prompt_path),
|
||||||
|
recover_last_session=False, # session recovery in handled by the interaction class
|
||||||
|
memory_compression=False,
|
||||||
|
model_provider=provider.get_model_name() if provider else None)
|
||||||
|
|
||||||
def get_today_date(self) -> str:
|
def get_today_date(self) -> str:
|
||||||
"""Get the date"""
|
"""Get the date"""
|
||||||
@@ -72,14 +77,14 @@ class BrowserAgent(Agent):
|
|||||||
def get_unvisited_links(self) -> List[str]:
|
def get_unvisited_links(self) -> List[str]:
|
||||||
return "\n".join([f"[{i}] {link}" for i, link in enumerate(self.navigable_links) if link not in self.search_history])
|
return "\n".join([f"[{i}] {link}" for i, link in enumerate(self.navigable_links) if link not in self.search_history])
|
||||||
|
|
||||||
def make_newsearch_prompt(self, user_prompt: str, search_result: dict) -> str:
|
def make_newsearch_prompt(self, prompt: str, search_result: dict) -> str:
|
||||||
search_choice = self.stringify_search_results(search_result)
|
search_choice = self.stringify_search_results(search_result)
|
||||||
self.logger.info(f"Search results: {search_choice}")
|
self.logger.info(f"Search results: {search_choice}")
|
||||||
return f"""
|
return f"""
|
||||||
Based on the search result:
|
Based on the search result:
|
||||||
{search_choice}
|
{search_choice}
|
||||||
Your goal is to find accurate and complete information to satisfy the user’s request.
|
Your goal is to find accurate and complete information to satisfy the user’s request.
|
||||||
User request: {user_prompt}
|
User request: {prompt}
|
||||||
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I will navigate to <link>"
|
To proceed, choose a relevant link from the search results. Announce your choice by saying: "I will navigate to <link>"
|
||||||
Do not explain your choice.
|
Do not explain your choice.
|
||||||
"""
|
"""
|
||||||
@@ -176,6 +181,7 @@ class BrowserAgent(Agent):
|
|||||||
animate_thinking("Thinking...", color="status")
|
animate_thinking("Thinking...", color="status")
|
||||||
self.memory.push('user', prompt)
|
self.memory.push('user', prompt)
|
||||||
answer, reasoning = await self.llm_request()
|
answer, reasoning = await self.llm_request()
|
||||||
|
self.last_reasoning = reasoning
|
||||||
if show_reasoning:
|
if show_reasoning:
|
||||||
pretty_print(reasoning, color="failure")
|
pretty_print(reasoning, color="failure")
|
||||||
pretty_print(answer, color="output")
|
pretty_print(answer, color="output")
|
||||||
@@ -229,15 +235,27 @@ class BrowserAgent(Agent):
|
|||||||
return links
|
return links
|
||||||
|
|
||||||
def select_link(self, links: List[str]) -> str | None:
|
def select_link(self, links: List[str]) -> str | None:
|
||||||
|
"""
|
||||||
|
Select the first unvisited link that is not the current page.
|
||||||
|
Preference is given to links not in search_history.
|
||||||
|
"""
|
||||||
for lk in links:
|
for lk in links:
|
||||||
if lk == self.current_page:
|
if lk == self.current_page or lk in self.search_history:
|
||||||
self.logger.info(f"Already visited {lk}. Skipping.")
|
self.logger.info(f"Skipping already visited or current link: {lk}")
|
||||||
continue
|
continue
|
||||||
self.logger.info(f"Selected link: {lk}")
|
self.logger.info(f"Selected link: {lk}")
|
||||||
return lk
|
return lk
|
||||||
self.logger.warning("No link selected.")
|
self.logger.warning("No suitable link selected.")
|
||||||
return None
|
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:
|
def conclude_prompt(self, user_query: str) -> str:
|
||||||
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
|
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
|
||||||
search_note = '\n'.join(annotated_notes)
|
search_note = '\n'.join(annotated_notes)
|
||||||
@@ -250,6 +268,7 @@ class BrowserAgent(Agent):
|
|||||||
|
|
||||||
Expand on the finding or step that lead to success, and provide a conclusion that answer the request. Include link when possible.
|
Expand on the finding or step that lead to success, and provide a conclusion that answer the request. Include link when possible.
|
||||||
Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way.
|
Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way.
|
||||||
|
You should answer in the same language as the user.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def search_prompt(self, user_prompt: str) -> str:
|
def search_prompt(self, user_prompt: str) -> str:
|
||||||
@@ -335,11 +354,13 @@ class BrowserAgent(Agent):
|
|||||||
self.show_search_results(search_result)
|
self.show_search_results(search_result)
|
||||||
prompt = self.make_newsearch_prompt(user_prompt, search_result)
|
prompt = self.make_newsearch_prompt(user_prompt, search_result)
|
||||||
unvisited = [None]
|
unvisited = [None]
|
||||||
while not complete and len(unvisited) > 0:
|
while not complete and len(unvisited) > 0 and not self.stop:
|
||||||
|
|
||||||
self.memory.clear()
|
self.memory.clear()
|
||||||
unvisited = self.select_unvisited(search_result)
|
unvisited = self.select_unvisited(search_result)
|
||||||
answer, reasoning = await self.llm_decide(prompt, show_reasoning = False)
|
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:
|
if self.last_answer == answer:
|
||||||
prompt = self.stuck_prompt(user_prompt, unvisited)
|
prompt = self.stuck_prompt(user_prompt, unvisited)
|
||||||
continue
|
continue
|
||||||
@@ -351,13 +372,13 @@ class BrowserAgent(Agent):
|
|||||||
self.status_message = "Filling web form..."
|
self.status_message = "Filling web form..."
|
||||||
pretty_print(f"Filling inputs form...", color="status")
|
pretty_print(f"Filling inputs form...", color="status")
|
||||||
fill_success = self.browser.fill_form(extracted_form)
|
fill_success = self.browser.fill_form(extracted_form)
|
||||||
page_text = self.browser.get_text()
|
page_text = self.get_page_text(limit_to_model_ctx=True)
|
||||||
answer = self.handle_update_prompt(user_prompt, page_text, fill_success)
|
answer = self.handle_update_prompt(user_prompt, page_text, fill_success)
|
||||||
answer, reasoning = await self.llm_decide(prompt)
|
answer, reasoning = await self.llm_decide(prompt)
|
||||||
|
|
||||||
if Action.FORM_FILLED.value in answer:
|
if Action.FORM_FILLED.value in answer:
|
||||||
pretty_print(f"Filled form. Handling page update.", color="status")
|
pretty_print(f"Filled form. Handling page update.", color="status")
|
||||||
page_text = self.browser.get_text()
|
page_text = self.get_page_text(limit_to_model_ctx=True)
|
||||||
self.navigable_links = self.browser.get_navigable()
|
self.navigable_links = self.browser.get_navigable()
|
||||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||||
continue
|
continue
|
||||||
@@ -379,7 +400,10 @@ class BrowserAgent(Agent):
|
|||||||
if (link == None and len(extracted_form) < 3) or Action.GO_BACK.value in answer or link in self.search_history:
|
if (link == None and len(extracted_form) < 3) or Action.GO_BACK.value in answer or link in self.search_history:
|
||||||
pretty_print(f"Going back to results. Still {len(unvisited)}", color="status")
|
pretty_print(f"Going back to results. Still {len(unvisited)}", color="status")
|
||||||
self.status_message = "Going back to search results..."
|
self.status_message = "Going back to search results..."
|
||||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
request_prompt = user_prompt
|
||||||
|
if link is None:
|
||||||
|
request_prompt += f"\nYou previously choosen:\n{self.last_answer} but the website is unavailable. Consider other options."
|
||||||
|
prompt = self.make_newsearch_prompt(request_prompt, unvisited)
|
||||||
self.search_history.append(link)
|
self.search_history.append(link)
|
||||||
self.current_page = link
|
self.current_page = link
|
||||||
continue
|
continue
|
||||||
@@ -393,7 +417,7 @@ class BrowserAgent(Agent):
|
|||||||
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
|
||||||
continue
|
continue
|
||||||
self.current_page = link
|
self.current_page = link
|
||||||
page_text = self.browser.get_text()
|
page_text = self.get_page_text(limit_to_model_ctx=True)
|
||||||
self.navigable_links = self.browser.get_navigable()
|
self.navigable_links = self.browser.get_navigable()
|
||||||
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
prompt = self.make_navigation_prompt(user_prompt, page_text)
|
||||||
self.status_message = "Navigating..."
|
self.status_message = "Navigating..."
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from sources.tools.searxSearch import searxSearch
|
|||||||
from sources.tools.flightSearch import FlightSearch
|
from sources.tools.flightSearch import FlightSearch
|
||||||
from sources.tools.fileFinder import FileFinder
|
from sources.tools.fileFinder import FileFinder
|
||||||
from sources.tools.BashInterpreter import BashInterpreter
|
from sources.tools.BashInterpreter import BashInterpreter
|
||||||
|
from sources.memory import Memory
|
||||||
|
|
||||||
class CasualAgent(Agent):
|
class CasualAgent(Agent):
|
||||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||||
@@ -17,6 +18,10 @@ class CasualAgent(Agent):
|
|||||||
} # No tools for the casual agent
|
} # No tools for the casual agent
|
||||||
self.role = "talk"
|
self.role = "talk"
|
||||||
self.type = "casual_agent"
|
self.type = "casual_agent"
|
||||||
|
self.memory = Memory(self.load_prompt(prompt_path),
|
||||||
|
recover_last_session=False, # session recovery in handled by the interaction class
|
||||||
|
memory_compression=False,
|
||||||
|
model_provider=provider.get_model_name())
|
||||||
|
|
||||||
async def process(self, prompt, speech_module) -> str:
|
async def process(self, prompt, speech_module) -> str:
|
||||||
self.memory.push('user', prompt)
|
self.memory.push('user', prompt)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from sources.tools.BashInterpreter import BashInterpreter
|
|||||||
from sources.tools.JavaInterpreter import JavaInterpreter
|
from sources.tools.JavaInterpreter import JavaInterpreter
|
||||||
from sources.tools.fileFinder import FileFinder
|
from sources.tools.fileFinder import FileFinder
|
||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
|
from sources.memory import Memory
|
||||||
|
|
||||||
class CoderAgent(Agent):
|
class CoderAgent(Agent):
|
||||||
"""
|
"""
|
||||||
@@ -29,6 +30,10 @@ class CoderAgent(Agent):
|
|||||||
self.role = "code"
|
self.role = "code"
|
||||||
self.type = "code_agent"
|
self.type = "code_agent"
|
||||||
self.logger = Logger("code_agent.log")
|
self.logger = Logger("code_agent.log")
|
||||||
|
self.memory = Memory(self.load_prompt(prompt_path),
|
||||||
|
recover_last_session=False, # session recovery in handled by the interaction class
|
||||||
|
memory_compression=False,
|
||||||
|
model_provider=provider.get_model_name())
|
||||||
|
|
||||||
def add_sys_info_prompt(self, prompt):
|
def add_sys_info_prompt(self, prompt):
|
||||||
"""Add system information to the prompt."""
|
"""Add system information to the prompt."""
|
||||||
@@ -41,15 +46,17 @@ class CoderAgent(Agent):
|
|||||||
async def process(self, prompt, speech_module) -> str:
|
async def process(self, prompt, speech_module) -> str:
|
||||||
answer = ""
|
answer = ""
|
||||||
attempt = 0
|
attempt = 0
|
||||||
max_attempts = 4
|
max_attempts = 5
|
||||||
prompt = self.add_sys_info_prompt(prompt)
|
prompt = self.add_sys_info_prompt(prompt)
|
||||||
self.memory.push('user', prompt)
|
self.memory.push('user', prompt)
|
||||||
clarify_trigger = "REQUEST_CLARIFICATION"
|
clarify_trigger = "REQUEST_CLARIFICATION"
|
||||||
|
|
||||||
while attempt < max_attempts:
|
while attempt < max_attempts and not self.stop:
|
||||||
|
print("Stopped?", self.stop)
|
||||||
animate_thinking("Thinking...", color="status")
|
animate_thinking("Thinking...", color="status")
|
||||||
await self.wait_message(speech_module)
|
await self.wait_message(speech_module)
|
||||||
answer, reasoning = await self.llm_request()
|
answer, reasoning = await self.llm_request()
|
||||||
|
self.last_reasoning = reasoning
|
||||||
if clarify_trigger in answer:
|
if clarify_trigger in answer:
|
||||||
self.last_answer = answer
|
self.last_answer = answer
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
@@ -62,14 +69,14 @@ class CoderAgent(Agent):
|
|||||||
animate_thinking("Executing code...", color="status")
|
animate_thinking("Executing code...", color="status")
|
||||||
self.status_message = "Executing code..."
|
self.status_message = "Executing code..."
|
||||||
self.logger.info(f"Attempt {attempt + 1}:\n{answer}")
|
self.logger.info(f"Attempt {attempt + 1}:\n{answer}")
|
||||||
exec_success, _ = self.execute_modules(answer)
|
exec_success, feedback = self.execute_modules(answer)
|
||||||
self.logger.info(f"Execution result: {exec_success}")
|
self.logger.info(f"Execution result: {exec_success}")
|
||||||
answer = self.remove_blocks(answer)
|
answer = self.remove_blocks(answer)
|
||||||
self.last_answer = answer
|
self.last_answer = answer
|
||||||
await asyncio.sleep(0)
|
await asyncio.sleep(0)
|
||||||
if exec_success and self.get_last_tool_type() != "bash":
|
if exec_success and self.get_last_tool_type() != "bash":
|
||||||
break
|
break
|
||||||
pretty_print("Execution failure", color="failure")
|
pretty_print(f"Execution failure:\n{feedback}", color="failure")
|
||||||
pretty_print("Correcting code...", color="status")
|
pretty_print("Correcting code...", color="status")
|
||||||
self.status_message = "Correcting code..."
|
self.status_message = "Correcting code..."
|
||||||
attempt += 1
|
attempt += 1
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from sources.utility import pretty_print, animate_thinking
|
|||||||
from sources.agents.agent import Agent
|
from sources.agents.agent import Agent
|
||||||
from sources.tools.fileFinder import FileFinder
|
from sources.tools.fileFinder import FileFinder
|
||||||
from sources.tools.BashInterpreter import BashInterpreter
|
from sources.tools.BashInterpreter import BashInterpreter
|
||||||
|
from sources.memory import Memory
|
||||||
|
|
||||||
class FileAgent(Agent):
|
class FileAgent(Agent):
|
||||||
def __init__(self, name, prompt_path, provider, verbose=False):
|
def __init__(self, name, prompt_path, provider, verbose=False):
|
||||||
@@ -18,15 +19,20 @@ class FileAgent(Agent):
|
|||||||
self.work_dir = self.tools["file_finder"].get_work_dir()
|
self.work_dir = self.tools["file_finder"].get_work_dir()
|
||||||
self.role = "files"
|
self.role = "files"
|
||||||
self.type = "file_agent"
|
self.type = "file_agent"
|
||||||
|
self.memory = Memory(self.load_prompt(prompt_path),
|
||||||
|
recover_last_session=False, # session recovery in handled by the interaction class
|
||||||
|
memory_compression=False,
|
||||||
|
model_provider=provider.get_model_name())
|
||||||
|
|
||||||
async def process(self, prompt, speech_module) -> str:
|
async def process(self, prompt, speech_module) -> str:
|
||||||
exec_success = False
|
exec_success = False
|
||||||
prompt += f"\nYou must work in directory: {self.work_dir}"
|
prompt += f"\nYou must work in directory: {self.work_dir}"
|
||||||
self.memory.push('user', prompt)
|
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)
|
await self.wait_message(speech_module)
|
||||||
animate_thinking("Thinking...", color="status")
|
animate_thinking("Thinking...", color="status")
|
||||||
answer, reasoning = await self.llm_request()
|
answer, reasoning = await self.llm_request()
|
||||||
|
self.last_reasoning = reasoning
|
||||||
exec_success, _ = self.execute_modules(answer)
|
exec_success, _ = self.execute_modules(answer)
|
||||||
answer = self.remove_blocks(answer)
|
answer = self.remove_blocks(answer)
|
||||||
self.last_answer = 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.text_to_speech import Speech
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
|
from sources.memory import Memory
|
||||||
|
|
||||||
class PlannerAgent(Agent):
|
class PlannerAgent(Agent):
|
||||||
def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
|
def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
|
||||||
@@ -29,6 +30,10 @@ class PlannerAgent(Agent):
|
|||||||
}
|
}
|
||||||
self.role = "planification"
|
self.role = "planification"
|
||||||
self.type = "planner_agent"
|
self.type = "planner_agent"
|
||||||
|
self.memory = Memory(self.load_prompt(prompt_path),
|
||||||
|
recover_last_session=False, # session recovery in handled by the interaction class
|
||||||
|
memory_compression=False,
|
||||||
|
model_provider=provider.get_model_name())
|
||||||
self.logger = Logger("planner_agent.log")
|
self.logger = Logger("planner_agent.log")
|
||||||
|
|
||||||
def get_task_names(self, text: str) -> List[str]:
|
def get_task_names(self, text: str) -> List[str]:
|
||||||
@@ -78,11 +83,15 @@ class PlannerAgent(Agent):
|
|||||||
self.logger.warning(f"Agent {task['agent']} does not exist.")
|
self.logger.warning(f"Agent {task['agent']} does not exist.")
|
||||||
pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
|
pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
|
||||||
return []
|
return []
|
||||||
|
try:
|
||||||
agent = {
|
agent = {
|
||||||
'agent': task['agent'],
|
'agent': task['agent'],
|
||||||
'id': task['id'],
|
'id': task['id'],
|
||||||
'task': task['task']
|
'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']}")
|
self.logger.info(f"Created agent {task['agent']} with task: {task['task']}")
|
||||||
if 'need' in task:
|
if 'need' in task:
|
||||||
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
|
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
|
||||||
@@ -151,7 +160,8 @@ class PlannerAgent(Agent):
|
|||||||
return []
|
return []
|
||||||
agents_tasks = self.parse_agent_tasks(answer)
|
agents_tasks = self.parse_agent_tasks(answer)
|
||||||
if agents_tasks == []:
|
if agents_tasks == []:
|
||||||
prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n"
|
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")
|
pretty_print("Failed to make plan. Retrying...", color="warning")
|
||||||
continue
|
continue
|
||||||
self.show_plan(agents_tasks, answer)
|
self.show_plan(agents_tasks, answer)
|
||||||
@@ -173,7 +183,11 @@ class PlannerAgent(Agent):
|
|||||||
last_agent_work = agents_work_result[id]
|
last_agent_work = agents_work_result[id]
|
||||||
tool_success_str = "success" if success else "failure"
|
tool_success_str = "success" if success else "failure"
|
||||||
pretty_print(f"Agent {id} work {tool_success_str}.", color="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."
|
next_task = "No task follow, this was the last step. If it failed add a task to recover."
|
||||||
else:
|
else:
|
||||||
next_task = f"Next task is: {agents_tasks[int(id)][0]}."
|
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)
|
agent_prompt = self.make_prompt(task['task'], required_infos)
|
||||||
pretty_print(f"Agent {task['agent']} started working...", color="status")
|
pretty_print(f"Agent {task['agent']} started working...", color="status")
|
||||||
self.logger.info(f"Agent {task['agent']} started working on {task['task']}.")
|
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_answer = answer
|
||||||
|
self.last_reasoning = reasoning
|
||||||
self.blocks_result = self.agents[task['agent'].lower()].blocks_result
|
self.blocks_result = self.agents[task['agent'].lower()].blocks_result
|
||||||
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
|
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
|
||||||
success = self.agents[task['agent'].lower()].get_success
|
success = self.agents[task['agent'].lower()].get_success
|
||||||
@@ -252,7 +267,7 @@ class PlannerAgent(Agent):
|
|||||||
return "Failed to parse the tasks.", ""
|
return "Failed to parse the tasks.", ""
|
||||||
i = 0
|
i = 0
|
||||||
steps = len(agents_tasks)
|
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]
|
task_name, task = agents_tasks[i][0], agents_tasks[i][1]
|
||||||
self.status_message = "Starting agents..."
|
self.status_message = "Starting agents..."
|
||||||
pretty_print(f"I will {task_name}.", color="info")
|
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)
|
answer, success = await self.start_agent_process(task, required_infos)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
|
if self.stop:
|
||||||
|
pretty_print(f"Requested stop.", color="failure")
|
||||||
agents_work_result[task['id']] = answer
|
agents_work_result[task['id']] = answer
|
||||||
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
|
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
|
||||||
steps = len(agents_tasks)
|
steps = len(agents_tasks)
|
||||||
|
|||||||
+155
-33
@@ -13,10 +13,13 @@ from fake_useragent import UserAgent
|
|||||||
from selenium_stealth import stealth
|
from selenium_stealth import stealth
|
||||||
import undetected_chromedriver as uc
|
import undetected_chromedriver as uc
|
||||||
import chromedriver_autoinstaller
|
import chromedriver_autoinstaller
|
||||||
|
import certifi
|
||||||
|
import ssl
|
||||||
import time
|
import time
|
||||||
import random
|
import random
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
|
import uuid
|
||||||
import tempfile
|
import tempfile
|
||||||
import markdownify
|
import markdownify
|
||||||
import sys
|
import sys
|
||||||
@@ -27,6 +30,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|||||||
from sources.utility import pretty_print, animate_thinking
|
from sources.utility import pretty_print, animate_thinking
|
||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
|
|
||||||
|
|
||||||
def get_chrome_path() -> str:
|
def get_chrome_path() -> str:
|
||||||
"""Get the path to the Chrome executable."""
|
"""Get the path to the Chrome executable."""
|
||||||
if sys.platform.startswith("win"):
|
if sys.platform.startswith("win"):
|
||||||
@@ -39,10 +43,17 @@ def get_chrome_path() -> str:
|
|||||||
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
|
||||||
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"]
|
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"]
|
||||||
else: # Linux
|
else: # Linux
|
||||||
paths = ["/usr/bin/google-chrome", "/usr/bin/chromium-browser", "/usr/bin/chromium", "/opt/chrome/chrome", "/usr/local/bin/chrome"]
|
paths = ["/usr/bin/google-chrome",
|
||||||
|
"/opt/chrome/chrome",
|
||||||
|
"/usr/bin/chromium-browser",
|
||||||
|
"/usr/bin/chromium",
|
||||||
|
"/usr/local/bin/chrome",
|
||||||
|
"/opt/google/chrome/chrome-headless-shell",
|
||||||
|
#"/app/chrome_bundle/chrome136/chrome-linux64"
|
||||||
|
]
|
||||||
|
|
||||||
for path in paths:
|
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
|
return path
|
||||||
print("Looking for Google Chrome in these locations failed:")
|
print("Looking for Google Chrome in these locations failed:")
|
||||||
print('\n'.join(paths))
|
print('\n'.join(paths))
|
||||||
@@ -59,13 +70,56 @@ def get_chrome_path() -> str:
|
|||||||
def get_random_user_agent() -> str:
|
def get_random_user_agent() -> str:
|
||||||
"""Get a random user agent string with associated vendor."""
|
"""Get a random user agent string with associated vendor."""
|
||||||
user_agents = [
|
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 (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/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "vendor": "Apple 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; rv:128.0) Gecko/20100101 Firefox/128.0", "vendor": ""},
|
{"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)
|
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:
|
||||||
|
print("ChromeDriver not found, attempting to install automatically...")
|
||||||
|
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."""
|
"""Create a Chrome WebDriver with specified options."""
|
||||||
chrome_options = Options()
|
chrome_options = Options()
|
||||||
chrome_path = get_chrome_path()
|
chrome_path = get_chrome_path()
|
||||||
@@ -75,55 +129,77 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx
|
|||||||
chrome_options.binary_location = chrome_path
|
chrome_options.binary_location = chrome_path
|
||||||
|
|
||||||
if headless:
|
if headless:
|
||||||
chrome_options.add_argument("--headless")
|
#chrome_options.add_argument("--headless")
|
||||||
|
chrome_options.add_argument("--headless=new")
|
||||||
chrome_options.add_argument("--disable-gpu")
|
chrome_options.add_argument("--disable-gpu")
|
||||||
chrome_options.add_argument("--disable-webgl")
|
chrome_options.add_argument("--disable-webgl")
|
||||||
user_data_dir = tempfile.mkdtemp()
|
user_data_dir = tempfile.mkdtemp()
|
||||||
user_agent = get_random_user_agent()
|
user_agent = get_random_user_agent()
|
||||||
chrome_options.add_argument(f"--user-data-dir={user_data_dir}")
|
width, height = (1920, 1080)
|
||||||
|
user_data_dir = tempfile.mkdtemp(prefix="chrome_profile_")
|
||||||
chrome_options.add_argument("--no-sandbox")
|
chrome_options.add_argument("--no-sandbox")
|
||||||
chrome_options.add_argument("--disable-dev-shm-usage")
|
chrome_options.add_argument('--disable-dev-shm-usage')
|
||||||
|
profile_dir = f"/tmp/chrome_profile_{uuid.uuid4().hex[:8]}"
|
||||||
|
chrome_options.add_argument(f'--user-data-dir={profile_dir}')
|
||||||
|
chrome_options.add_argument(f"--accept-lang={lang}-{lang.upper()},{lang};q=0.9")
|
||||||
|
chrome_options.add_argument("--disable-extensions")
|
||||||
|
chrome_options.add_argument("--disable-background-timer-throttling")
|
||||||
|
chrome_options.add_argument("--timezone=Europe/Paris")
|
||||||
|
chrome_options.add_argument('--remote-debugging-port=9222')
|
||||||
|
chrome_options.add_argument('--disable-background-timer-throttling')
|
||||||
|
chrome_options.add_argument('--disable-backgrounding-occluded-windows')
|
||||||
|
chrome_options.add_argument('--disable-renderer-backgrounding')
|
||||||
|
chrome_options.add_argument('--disable-features=TranslateUI')
|
||||||
|
chrome_options.add_argument('--disable-ipc-flooding-protection')
|
||||||
chrome_options.add_argument("--mute-audio")
|
chrome_options.add_argument("--mute-audio")
|
||||||
chrome_options.add_argument("--disable-notifications")
|
chrome_options.add_argument("--disable-notifications")
|
||||||
chrome_options.add_argument("--autoplay-policy=user-gesture-required")
|
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("--disable-blink-features=AutomationControlled")
|
||||||
chrome_options.add_argument(f'user-agent={user_agent["ua"]}')
|
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}')
|
chrome_options.add_argument(f'--window-size={width},{height}')
|
||||||
if not stealth_mode:
|
if not stealth_mode:
|
||||||
# crx file can't be installed in stealth mode
|
|
||||||
if not os.path.exists(crx_path):
|
if not os.path.exists(crx_path):
|
||||||
pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure")
|
pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure")
|
||||||
else:
|
else:
|
||||||
chrome_options.add_extension(crx_path)
|
chrome_options.add_extension(crx_path)
|
||||||
|
|
||||||
chromedriver_path = shutil.which("chromedriver")
|
chromedriver_path = install_chromedriver()
|
||||||
if not chromedriver_path:
|
|
||||||
chromedriver_path = chromedriver_autoinstaller.install()
|
|
||||||
|
|
||||||
if not chromedriver_path:
|
|
||||||
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
|
|
||||||
|
|
||||||
service = Service(chromedriver_path)
|
service = Service(chromedriver_path)
|
||||||
if stealth_mode:
|
if stealth_mode:
|
||||||
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
|
||||||
driver = uc.Chrome(service=service, options=chrome_options)
|
driver = create_undetected_chromedriver(service, chrome_options)
|
||||||
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
|
|
||||||
chrome_version = driver.capabilities['browserVersion']
|
chrome_version = driver.capabilities['browserVersion']
|
||||||
stealth(driver,
|
stealth(driver,
|
||||||
languages=["en-US", "en"],
|
languages=["en-US", "en"],
|
||||||
vendor=user_agent["vendor"],
|
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.",
|
webgl_vendor="Intel Inc.",
|
||||||
renderer="Intel Iris OpenGL Engine",
|
renderer="Intel Iris OpenGL Engine",
|
||||||
fix_hairline=True,
|
fix_hairline=True,
|
||||||
)
|
)
|
||||||
return driver
|
return driver
|
||||||
security_prefs = {
|
security_prefs = {
|
||||||
"profile.default_content_setting_values.media_stream": 2,
|
"profile.default_content_setting_values.geolocation": 0,
|
||||||
"profile.default_content_setting_values.geolocation": 2,
|
"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,
|
"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("prefs", security_prefs)
|
||||||
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
|
||||||
@@ -144,12 +220,17 @@ class Browser:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Failed to initialize browser: {str(e)}")
|
raise Exception(f"Failed to initialize browser: {str(e)}")
|
||||||
self.setup_tabs()
|
self.setup_tabs()
|
||||||
|
self.patch_browser_fingerprint()
|
||||||
if anticaptcha_manual_install:
|
if anticaptcha_manual_install:
|
||||||
self.load_anticatpcha_manually()
|
self.load_anticatpcha_manually()
|
||||||
|
|
||||||
def setup_tabs(self):
|
def setup_tabs(self):
|
||||||
self.tabs = self.driver.window_handles
|
self.tabs = self.driver.window_handles
|
||||||
|
try:
|
||||||
self.driver.get("https://www.google.com")
|
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()
|
self.screenshot()
|
||||||
|
|
||||||
def switch_control_tab(self):
|
def switch_control_tab(self):
|
||||||
@@ -158,14 +239,40 @@ class Browser:
|
|||||||
|
|
||||||
def load_anticatpcha_manually(self):
|
def load_anticatpcha_manually(self):
|
||||||
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
|
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
|
||||||
|
try:
|
||||||
self.driver.get(self.anticaptcha)
|
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:
|
def go_to(self, url:str) -> bool:
|
||||||
"""Navigate to a specified URL."""
|
"""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:
|
try:
|
||||||
initial_handles = self.driver.window_handles
|
initial_handles = self.driver.window_handles
|
||||||
self.driver.get(url)
|
self.driver.get(url)
|
||||||
|
time.sleep(random.uniform(0.01, 0.3))
|
||||||
try:
|
try:
|
||||||
wait = WebDriverWait(self.driver, timeout=10)
|
wait = WebDriverWait(self.driver, timeout=10)
|
||||||
wait.until(
|
wait.until(
|
||||||
@@ -177,6 +284,8 @@ class Browser:
|
|||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'")
|
self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'")
|
||||||
self.apply_web_safety()
|
self.apply_web_safety()
|
||||||
|
time.sleep(random.uniform(0.01, 0.2))
|
||||||
|
self.human_scroll()
|
||||||
self.logger.log(f"Navigated to: {url}")
|
self.logger.log(f"Navigated to: {url}")
|
||||||
return True
|
return True
|
||||||
except TimeoutException as e:
|
except TimeoutException as e:
|
||||||
@@ -226,7 +335,7 @@ class Browser:
|
|||||||
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
|
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
|
||||||
self.logger.info(f"Extracted text: {result[:100]}...")
|
self.logger.info(f"Extracted text: {result[:100]}...")
|
||||||
self.logger.info(f"Extracted text length: {len(result)}")
|
self.logger.info(f"Extracted text length: {len(result)}")
|
||||||
return result[:8192]
|
return result[:32768]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.error(f"Error getting text: {str(e)}")
|
self.logger.error(f"Error getting text: {str(e)}")
|
||||||
return None
|
return None
|
||||||
@@ -576,19 +685,24 @@ class Browser:
|
|||||||
return self.screenshot_folder + "/updated_screen.png"
|
return self.screenshot_folder + "/updated_screen.png"
|
||||||
|
|
||||||
def screenshot(self, filename:str = 'updated_screen.png') -> bool:
|
def screenshot(self, filename:str = 'updated_screen.png') -> bool:
|
||||||
"""Take a screenshot of the current page."""
|
"""Take a screenshot of the current page, attempt to capture the full page by zooming out."""
|
||||||
self.logger.info("Taking screenshot...")
|
self.logger.info("Taking full page screenshot...")
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
try:
|
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)
|
path = os.path.join(self.screenshot_folder, filename)
|
||||||
if not os.path.exists(self.screenshot_folder):
|
if not os.path.exists(self.screenshot_folder):
|
||||||
os.makedirs(self.screenshot_folder)
|
os.makedirs(self.screenshot_folder)
|
||||||
self.driver.save_screenshot(path)
|
self.driver.save_screenshot(path)
|
||||||
self.logger.info(f"Screenshot saved as {filename}")
|
self.logger.info(f"Full page screenshot saved as {filename}")
|
||||||
return True
|
|
||||||
except Exception as e:
|
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
|
return False
|
||||||
|
finally:
|
||||||
|
self.driver.execute_script(f"document.body.style.zoom='1'")
|
||||||
|
return True
|
||||||
|
|
||||||
def apply_web_safety(self):
|
def apply_web_safety(self):
|
||||||
"""
|
"""
|
||||||
@@ -599,17 +713,25 @@ class Browser:
|
|||||||
input_elements = self.driver.execute_script(script)
|
input_elements = self.driver.execute_script(script)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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)
|
browser = Browser(driver, anticaptcha_manual_install=True)
|
||||||
|
|
||||||
input("press enter to continue")
|
input("press enter to continue")
|
||||||
print("AntiCaptcha / Form Test")
|
print("AntiCaptcha / Form Test")
|
||||||
#browser.go_to("https://www.browserscan.net/bot-detection")
|
browser.go_to("https://bot.sannysoft.com")
|
||||||
|
time.sleep(5)
|
||||||
#txt = browser.get_text()
|
#txt = browser.get_text()
|
||||||
#browser.go_to("https://www.google.com/recaptcha/api2/demo")
|
|
||||||
browser.go_to("https://home.openweathermap.org/users/sign_up")
|
browser.go_to("https://home.openweathermap.org/users/sign_up")
|
||||||
inputs_visible = browser.get_form_inputs()
|
inputs_visible = browser.get_form_inputs()
|
||||||
print("inputs:", inputs_visible)
|
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)']
|
#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)
|
#browser.fill_form(inputs_fill)
|
||||||
input("press enter to exit")
|
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.utility import pretty_print, animate_thinking
|
||||||
from sources.router import AgentRouter
|
from sources.router import AgentRouter
|
||||||
from sources.speech_to_text import AudioTranscriber, AudioRecorder
|
from sources.speech_to_text import AudioTranscriber, AudioRecorder
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
class Interaction:
|
class Interaction:
|
||||||
@@ -21,6 +22,7 @@ class Interaction:
|
|||||||
self.current_agent = None
|
self.current_agent = None
|
||||||
self.last_query = None
|
self.last_query = None
|
||||||
self.last_answer = None
|
self.last_answer = None
|
||||||
|
self.last_reasoning = None
|
||||||
self.agents = agents
|
self.agents = agents
|
||||||
self.tts_enabled = tts_enabled
|
self.tts_enabled = tts_enabled
|
||||||
self.stt_enabled = stt_enabled
|
self.stt_enabled = stt_enabled
|
||||||
@@ -31,6 +33,7 @@ class Interaction:
|
|||||||
self.transcriber = None
|
self.transcriber = None
|
||||||
self.recorder = None
|
self.recorder = None
|
||||||
self.is_generating = False
|
self.is_generating = False
|
||||||
|
self.languages = langs
|
||||||
if tts_enabled:
|
if tts_enabled:
|
||||||
self.initialize_tts()
|
self.initialize_tts()
|
||||||
if stt_enabled:
|
if stt_enabled:
|
||||||
@@ -39,11 +42,16 @@ class Interaction:
|
|||||||
self.load_last_session()
|
self.load_last_session()
|
||||||
self.emit_status()
|
self.emit_status()
|
||||||
|
|
||||||
|
def get_spoken_language(self) -> str:
|
||||||
|
"""Get the primary TTS language."""
|
||||||
|
lang = self.languages[0]
|
||||||
|
return lang
|
||||||
|
|
||||||
def initialize_tts(self):
|
def initialize_tts(self):
|
||||||
"""Initialize TTS."""
|
"""Initialize TTS."""
|
||||||
if not self.speech:
|
if not self.speech:
|
||||||
animate_thinking("Initializing text-to-speech...", color="status")
|
animate_thinking("Initializing text-to-speech...", color="status")
|
||||||
self.speech = Speech(enable=self.tts_enabled)
|
self.speech = Speech(enable=self.tts_enabled, language=self.get_spoken_language(), voice_idx=1)
|
||||||
|
|
||||||
def initialize_stt(self):
|
def initialize_stt(self):
|
||||||
"""Initialize STT."""
|
"""Initialize STT."""
|
||||||
@@ -133,6 +141,11 @@ class Interaction:
|
|||||||
self.last_query = query
|
self.last_query = query
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
def set_query(self, query: str) -> None:
|
||||||
|
"""Set the query"""
|
||||||
|
self.is_active = True
|
||||||
|
self.last_query = query
|
||||||
|
|
||||||
async def think(self) -> bool:
|
async def think(self) -> bool:
|
||||||
"""Request AI agents to process the user input."""
|
"""Request AI agents to process the user input."""
|
||||||
push_last_agent_memory = False
|
push_last_agent_memory = False
|
||||||
@@ -146,7 +159,7 @@ class Interaction:
|
|||||||
tmp = self.last_answer
|
tmp = self.last_answer
|
||||||
self.current_agent = agent
|
self.current_agent = agent
|
||||||
self.is_generating = True
|
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
|
self.is_generating = False
|
||||||
if push_last_agent_memory:
|
if push_last_agent_memory:
|
||||||
self.current_agent.memory.push('user', self.last_query)
|
self.current_agent.memory.push('user', self.last_query)
|
||||||
@@ -167,12 +180,20 @@ class Interaction:
|
|||||||
return None
|
return None
|
||||||
return self.current_agent.get_last_block_answer()
|
return self.current_agent.get_last_block_answer()
|
||||||
|
|
||||||
|
def speak_answer(self) -> None:
|
||||||
|
"""Speak the answer to the user in a non-blocking thread."""
|
||||||
|
if self.last_query is None:
|
||||||
|
return
|
||||||
|
if self.tts_enabled and self.last_answer and self.speech:
|
||||||
|
def speak_in_thread(speech_instance, text):
|
||||||
|
speech_instance.speak(text)
|
||||||
|
thread = threading.Thread(target=speak_in_thread, args=(self.speech, self.last_answer))
|
||||||
|
thread.start()
|
||||||
|
|
||||||
def show_answer(self) -> None:
|
def show_answer(self) -> None:
|
||||||
"""Show the answer to the user."""
|
"""Show the answer to the user."""
|
||||||
if self.last_query is None:
|
if self.last_query is None:
|
||||||
return
|
return
|
||||||
if self.current_agent is not None:
|
if self.current_agent is not None:
|
||||||
self.current_agent.show_answer()
|
self.current_agent.show_answer()
|
||||||
if self.tts_enabled and self.last_answer:
|
|
||||||
self.speech.speak(self.last_answer)
|
|
||||||
|
|
||||||
|
|||||||
+3
-43
@@ -1,8 +1,6 @@
|
|||||||
from typing import List, Tuple, Type, Dict
|
from typing import List, Tuple, Type, Dict
|
||||||
import re
|
import re
|
||||||
import langid
|
import langid
|
||||||
import nltk
|
|
||||||
from nltk.sentiment.vader import SentimentIntensityAnalyzer
|
|
||||||
from transformers import MarianMTModel, MarianTokenizer
|
from transformers import MarianMTModel, MarianTokenizer
|
||||||
|
|
||||||
from sources.utility import pretty_print, animate_thinking
|
from sources.utility import pretty_print, animate_thinking
|
||||||
@@ -16,7 +14,6 @@ class LanguageUtility:
|
|||||||
args:
|
args:
|
||||||
supported_language: list of languages for translation, determine which Helsinki-NLP model to load
|
supported_language: list of languages for translation, determine which Helsinki-NLP model to load
|
||||||
"""
|
"""
|
||||||
self.sid = None
|
|
||||||
self.translators_tokenizer = None
|
self.translators_tokenizer = None
|
||||||
self.translators_model = None
|
self.translators_model = None
|
||||||
self.logger = Logger("language.log")
|
self.logger = Logger("language.log")
|
||||||
@@ -25,11 +22,6 @@ class LanguageUtility:
|
|||||||
|
|
||||||
def load_model(self) -> None:
|
def load_model(self) -> None:
|
||||||
animate_thinking("Loading language utility...", color="status")
|
animate_thinking("Loading language utility...", color="status")
|
||||||
try:
|
|
||||||
nltk.data.find('vader_lexicon')
|
|
||||||
except LookupError:
|
|
||||||
nltk.download('vader_lexicon')
|
|
||||||
self.sid = SentimentIntensityAnalyzer()
|
|
||||||
self.translators_tokenizer = {lang: MarianTokenizer.from_pretrained(f"Helsinki-NLP/opus-mt-{lang}-en") for lang in self.supported_language if lang != "en"}
|
self.translators_tokenizer = {lang: MarianTokenizer.from_pretrained(f"Helsinki-NLP/opus-mt-{lang}-en") for lang in self.supported_language if lang != "en"}
|
||||||
self.translators_model = {lang: MarianMTModel.from_pretrained(f"Helsinki-NLP/opus-mt-{lang}-en") for lang in self.supported_language if lang != "en"}
|
self.translators_model = {lang: MarianMTModel.from_pretrained(f"Helsinki-NLP/opus-mt-{lang}-en") for lang in self.supported_language if lang != "en"}
|
||||||
|
|
||||||
@@ -65,49 +57,17 @@ class LanguageUtility:
|
|||||||
translation = model.generate(**inputs)
|
translation = model.generate(**inputs)
|
||||||
return tokenizer.decode(translation[0], skip_special_tokens=True)
|
return tokenizer.decode(translation[0], skip_special_tokens=True)
|
||||||
|
|
||||||
def detect_emotion(self, text: str) -> str:
|
|
||||||
"""
|
|
||||||
Detect the dominant emotion in the given text
|
|
||||||
Args:
|
|
||||||
text: string to analyze
|
|
||||||
Returns: string of the dominant emotion
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
scores = self.sid.polarity_scores(text)
|
|
||||||
emotions = {
|
|
||||||
'Happy': max(scores['pos'], 0),
|
|
||||||
'Angry': 0,
|
|
||||||
'Sad': max(scores['neg'], 0),
|
|
||||||
'Fear': 0,
|
|
||||||
'Surprise': 0
|
|
||||||
}
|
|
||||||
if scores['compound'] < -0.5:
|
|
||||||
emotions['Angry'] = abs(scores['compound']) * 0.5
|
|
||||||
emotions['Fear'] = abs(scores['compound']) * 0.5
|
|
||||||
elif scores['compound'] > 0.5:
|
|
||||||
emotions['Happy'] = scores['compound']
|
|
||||||
emotions['Surprise'] = scores['compound'] * 0.5
|
|
||||||
dominant_emotion = max(emotions, key=emotions.get)
|
|
||||||
if emotions[dominant_emotion] == 0:
|
|
||||||
return 'Neutral'
|
|
||||||
self.logger.info(f"Emotion: {dominant_emotion} for text: {text}")
|
|
||||||
return dominant_emotion
|
|
||||||
except Exception as e:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
def analyze(self, text):
|
def analyze(self, text):
|
||||||
"""
|
"""
|
||||||
Combined analysis of language and emotion
|
Combined analysis of language and emotion
|
||||||
Args:
|
Args:
|
||||||
text: string to analyze
|
text: string to analyze
|
||||||
Returns: dictionary with language and emotion results
|
Returns: dictionary with language related information
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
language = self.detect_language(text)
|
language = self.detect_language(text)
|
||||||
emotions = self.detect_emotion(text)
|
|
||||||
return {
|
return {
|
||||||
"language": language,
|
"language": language
|
||||||
"emotions": emotions
|
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
@@ -125,4 +85,4 @@ if __name__ == "__main__":
|
|||||||
pretty_print(f"Language: {detector.detect_language(text)}", color="status")
|
pretty_print(f"Language: {detector.detect_language(text)}", color="status")
|
||||||
result = detector.analyze(text)
|
result = detector.analyze(text)
|
||||||
trans = detector.translate(text, result['language'])
|
trans = detector.translate(text, result['language'])
|
||||||
pretty_print(f"Translation: {trans} - from: {result['language']} - Emotion: {result['emotions']}")
|
pretty_print(f"Translation: {trans} - from: {result['language']}")
|
||||||
+107
-33
@@ -1,27 +1,26 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import ollama
|
|
||||||
from ollama import chat
|
|
||||||
import requests
|
|
||||||
import subprocess
|
|
||||||
import ipaddress
|
|
||||||
import httpx
|
|
||||||
import socket
|
|
||||||
import platform
|
import platform
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
from urllib.parse import urlparse
|
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 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.logger import Logger
|
||||||
|
from sources.utility import pretty_print, animate_thinking
|
||||||
|
|
||||||
class Provider:
|
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.provider_name = provider_name.lower()
|
||||||
self.model = model
|
self.model = model
|
||||||
self.is_local = is_local
|
self.is_local = is_local
|
||||||
self.server_ip = server_address
|
self.server_ip = server_address
|
||||||
|
self.server_address = server_address
|
||||||
self.available_providers = {
|
self.available_providers = {
|
||||||
"ollama": self.ollama_fn,
|
"ollama": self.ollama_fn,
|
||||||
"server": self.server_fn,
|
"server": self.server_fn,
|
||||||
@@ -32,11 +31,12 @@ class Provider:
|
|||||||
"deepseek": self.deepseek_fn,
|
"deepseek": self.deepseek_fn,
|
||||||
"together": self.together_fn,
|
"together": self.together_fn,
|
||||||
"dsk_deepseek": self.dsk_deepseek,
|
"dsk_deepseek": self.dsk_deepseek,
|
||||||
|
"openrouter": self.openrouter_fn,
|
||||||
"test": self.test_fn
|
"test": self.test_fn
|
||||||
}
|
}
|
||||||
self.logger = Logger("provider.log")
|
self.logger = Logger("provider.log")
|
||||||
self.api_key = None
|
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:
|
if self.provider_name not in self.available_providers:
|
||||||
raise ValueError(f"Unknown provider: {provider_name}")
|
raise ValueError(f"Unknown provider: {provider_name}")
|
||||||
if self.provider_name in self.unsafe_providers and self.is_local == False:
|
if self.provider_name in self.unsafe_providers and self.is_local == False:
|
||||||
@@ -45,6 +45,9 @@ class Provider:
|
|||||||
elif self.provider_name != "ollama":
|
elif self.provider_name != "ollama":
|
||||||
pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success")
|
pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success")
|
||||||
|
|
||||||
|
def get_model_name(self) -> str:
|
||||||
|
return self.model
|
||||||
|
|
||||||
def get_api_key(self, provider):
|
def get_api_key(self, provider):
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
api_key_var = f"{provider.upper()}_API_KEY"
|
api_key_var = f"{provider.upper()}_API_KEY"
|
||||||
@@ -54,7 +57,7 @@ class Provider:
|
|||||||
exit(1)
|
exit(1)
|
||||||
return api_key
|
return api_key
|
||||||
|
|
||||||
def respond(self, history, verbose = True):
|
def respond(self, history, verbose=True):
|
||||||
"""
|
"""
|
||||||
Use the choosen provider to generate text.
|
Use the choosen provider to generate text.
|
||||||
"""
|
"""
|
||||||
@@ -70,8 +73,11 @@ class Provider:
|
|||||||
except AttributeError as e:
|
except AttributeError as e:
|
||||||
raise NotImplementedError(f"{str(e)}\nIs {self.provider_name} implemented ?")
|
raise NotImplementedError(f"{str(e)}\nIs {self.provider_name} implemented ?")
|
||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError(f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
|
raise ModuleNotFoundError(
|
||||||
|
f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
if "try again later" in str(e).lower():
|
||||||
|
return f"{self.provider_name} server is overloaded. Please try again later."
|
||||||
if "refused" in str(e):
|
if "refused" in str(e):
|
||||||
return f"Server {self.server_ip} seem offline. Unable to answer."
|
return f"Server {self.server_ip} seem offline. Unable to answer."
|
||||||
raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e
|
raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e
|
||||||
@@ -101,8 +107,7 @@ class Provider:
|
|||||||
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e:
|
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e:
|
||||||
return False
|
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.
|
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")
|
pretty_print(f"An error occurred: {str(e)}", color="failure")
|
||||||
break
|
break
|
||||||
except KeyError as e:
|
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:
|
except Exception as e:
|
||||||
raise e
|
raise e
|
||||||
return thought
|
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 = ""
|
thought = ""
|
||||||
|
host = "http://localhost:11434" if self.is_local else f"http://{self.server_address}"
|
||||||
|
client = OllamaClient(host=host)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stream = chat(
|
stream = client.chat(
|
||||||
model=self.model,
|
model=self.model,
|
||||||
messages=history,
|
messages=history,
|
||||||
stream=True,
|
stream=True,
|
||||||
)
|
)
|
||||||
for chunk in stream:
|
for chunk in stream:
|
||||||
if verbose:
|
if verbose:
|
||||||
print(chunk['message']['content'], end='', flush=True)
|
print(chunk["message"]["content"], end="", flush=True)
|
||||||
thought += chunk['message']['content']
|
thought += chunk["message"]["content"]
|
||||||
except httpx.ConnectError as e:
|
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
|
raise Exception(
|
||||||
except ollama.ResponseError as e:
|
f"\nOllama connection failed at {host}. Check if the server is running."
|
||||||
if e.status_code == 404:
|
) from e
|
||||||
|
except Exception as e:
|
||||||
|
if hasattr(e, 'status_code') and e.status_code == 404:
|
||||||
animate_thinking(f"Downloading {self.model}...")
|
animate_thinking(f"Downloading {self.model}...")
|
||||||
ollama.pull(self.model)
|
client.pull(self.model)
|
||||||
self.ollama_fn(history, verbose)
|
self.ollama_fn(history, verbose)
|
||||||
if "refused" in str(e).lower():
|
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
|
raise e
|
||||||
|
|
||||||
return thought
|
return thought
|
||||||
|
|
||||||
def huggingface_fn(self, history, verbose=False):
|
def huggingface_fn(self, history, verbose=False):
|
||||||
@@ -208,13 +222,45 @@ class Provider:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"OpenAI API error: {str(e)}") from 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):
|
def google_fn(self, history, verbose=False):
|
||||||
"""
|
"""
|
||||||
Use google gemini to generate text.
|
Use google gemini to generate text.
|
||||||
"""
|
"""
|
||||||
base_url = self.server_ip
|
base_url = self.server_ip
|
||||||
if self.is_local:
|
if self.is_local:
|
||||||
raise Exception("Google Gemini is not available for local use.")
|
raise Exception("Google Gemini is not available for local use. Change config.ini")
|
||||||
|
|
||||||
client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/")
|
client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/")
|
||||||
try:
|
try:
|
||||||
@@ -237,6 +283,8 @@ class Provider:
|
|||||||
"""
|
"""
|
||||||
from together import Together
|
from together import Together
|
||||||
client = Together(api_key=self.api_key)
|
client = Together(api_key=self.api_key)
|
||||||
|
if self.is_local:
|
||||||
|
raise Exception("Together AI is not available for local use. Change config.ini")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
@@ -257,6 +305,8 @@ class Provider:
|
|||||||
Use deepseek api to generate text.
|
Use deepseek api to generate text.
|
||||||
"""
|
"""
|
||||||
client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
|
client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
|
||||||
|
if self.is_local:
|
||||||
|
raise Exception("Deepseek (API) is not available for local use. Change config.ini")
|
||||||
try:
|
try:
|
||||||
response = client.chat.completions.create(
|
response = client.chat.completions.create(
|
||||||
model="deepseek-chat",
|
model="deepseek-chat",
|
||||||
@@ -270,7 +320,7 @@ class Provider:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"Deepseek API error: {str(e)}") from 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.
|
Use local lm-studio server to generate text.
|
||||||
lm studio use endpoint /v1/chat/completions not /chat/completions like openai
|
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
|
raise Exception(f"An error occurred: {str(e)}") from e
|
||||||
return thought
|
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
|
Use: xtekky/deepseek4free
|
||||||
For free api. Api key should be set to DSK_DEEPSEEK_API_KEY
|
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
|
raise APIError(f"API error occurred: {str(e)}") from e
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def test_fn(self, history, verbose = True):
|
def test_fn(self, history, verbose=True):
|
||||||
"""
|
"""
|
||||||
This function is used to conduct tests.
|
This function is used to conduct tests.
|
||||||
"""
|
"""
|
||||||
@@ -340,6 +413,7 @@ class Provider:
|
|||||||
"""
|
"""
|
||||||
return thought
|
return thought
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
provider = Provider("server", "deepseek-r1:32b", " x.x.x.x:8080")
|
provider = Provider("server", "deepseek-r1:32b", " x.x.x.x:8080")
|
||||||
res = provider.respond(["user", "Hello, how are you?"])
|
res = provider.respond(["user", "Hello, how are you?"])
|
||||||
|
|||||||
+3
-1
@@ -17,12 +17,14 @@ class Logger:
|
|||||||
def create_logging(self, log_filename):
|
def create_logging(self, log_filename):
|
||||||
self.logger = logging.getLogger(log_filename)
|
self.logger = logging.getLogger(log_filename)
|
||||||
self.logger.setLevel(logging.DEBUG)
|
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)
|
file_handler = logging.FileHandler(self.log_path)
|
||||||
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
self.logger.addHandler(file_handler)
|
self.logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
|
||||||
def create_folder(self, path):
|
def create_folder(self, path):
|
||||||
"""Create log dir"""
|
"""Create log dir"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
+93
-20
@@ -8,7 +8,7 @@ from typing import List, Tuple, Type, Dict
|
|||||||
import torch
|
import torch
|
||||||
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
|
||||||
|
|
||||||
from sources.utility import timer_decorator, pretty_print
|
from sources.utility import timer_decorator, pretty_print, animate_thinking
|
||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
|
|
||||||
class Memory():
|
class Memory():
|
||||||
@@ -18,8 +18,8 @@ class Memory():
|
|||||||
"""
|
"""
|
||||||
def __init__(self, system_prompt: str,
|
def __init__(self, system_prompt: str,
|
||||||
recover_last_session: bool = False,
|
recover_last_session: bool = False,
|
||||||
memory_compression: bool = True):
|
memory_compression: bool = True,
|
||||||
self.memory = []
|
model_provider: str = "deepseek-r1:14b"):
|
||||||
self.memory = [{'role': 'system', 'content': system_prompt}]
|
self.memory = [{'role': 'system', 'content': system_prompt}]
|
||||||
|
|
||||||
self.logger = Logger("memory.log")
|
self.logger = Logger("memory.log")
|
||||||
@@ -31,22 +31,44 @@ class Memory():
|
|||||||
self.load_memory()
|
self.load_memory()
|
||||||
self.session_recovered = True
|
self.session_recovered = True
|
||||||
# memory compression system
|
# memory compression system
|
||||||
self.model = "pszemraj/led-base-book-summary"
|
self.model = None
|
||||||
|
self.tokenizer = None
|
||||||
self.device = self.get_cuda_device()
|
self.device = self.get_cuda_device()
|
||||||
self.memory_compression = memory_compression
|
self.memory_compression = memory_compression
|
||||||
self.tokenizer = None
|
self.model_provider = model_provider
|
||||||
self.model = None
|
|
||||||
if self.memory_compression:
|
if self.memory_compression:
|
||||||
self.download_model()
|
self.download_model()
|
||||||
|
|
||||||
|
def get_ideal_ctx(self, model_name: str) -> int | None:
|
||||||
|
"""
|
||||||
|
Estimate context size based on the model name.
|
||||||
|
EXPERIMENTAL for memory compression
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
import math
|
||||||
|
|
||||||
|
def extract_number_before_b(sentence: str) -> int:
|
||||||
|
match = re.search(r'(\d+)b', sentence, re.IGNORECASE)
|
||||||
|
return int(match.group(1)) if match else None
|
||||||
|
|
||||||
|
model_size = extract_number_before_b(model_name)
|
||||||
|
if not model_size:
|
||||||
|
return None
|
||||||
|
base_size = 7 # Base model size in billions
|
||||||
|
base_context = 4096 # Base context size in tokens
|
||||||
|
scaling_factor = 1.5 # Approximate scaling factor for context size growth
|
||||||
|
context_size = int(base_context * (model_size / base_size) ** scaling_factor)
|
||||||
|
context_size = 2 ** round(math.log2(context_size))
|
||||||
|
self.logger.info(f"Estimated context size for {model_name}: {context_size} tokens.")
|
||||||
|
return context_size
|
||||||
|
|
||||||
def download_model(self):
|
def download_model(self):
|
||||||
"""Download the model if not already downloaded."""
|
"""Download the model if not already downloaded."""
|
||||||
pretty_print("Downloading memory compression model...", color="status")
|
animate_thinking("Loading memory compression model...", color="status")
|
||||||
self.tokenizer = AutoTokenizer.from_pretrained(self.model)
|
self.tokenizer = AutoTokenizer.from_pretrained("pszemraj/led-base-book-summary")
|
||||||
self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model)
|
self.model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-base-book-summary")
|
||||||
self.logger.info("Memory compression system initialized.")
|
self.logger.info("Memory compression system initialized.")
|
||||||
|
|
||||||
|
|
||||||
def get_filename(self) -> str:
|
def get_filename(self) -> str:
|
||||||
"""Get the filename for the save file."""
|
"""Get the filename for the save file."""
|
||||||
return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt"
|
return f"memory_{self.session_time.strftime('%Y-%m-%d_%H-%M-%S')}.txt"
|
||||||
@@ -79,6 +101,32 @@ class Memory():
|
|||||||
return saved_sessions[0][0]
|
return saved_sessions[0][0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def save_json_file(self, path: str, json_memory: dict) -> None:
|
||||||
|
"""Save a JSON file."""
|
||||||
|
try:
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
json.dump(json_memory, f)
|
||||||
|
self.logger.info(f"Saved memory json at {path}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.warning(f"Error saving file {path}: {e}")
|
||||||
|
|
||||||
|
def load_json_file(self, path: str) -> dict:
|
||||||
|
"""Load a JSON file."""
|
||||||
|
json_memory = {}
|
||||||
|
try:
|
||||||
|
with open(path, 'r') as f:
|
||||||
|
json_memory = json.load(f)
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.logger.warning(f"File not found: {path}")
|
||||||
|
return {}
|
||||||
|
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:
|
def load_memory(self, agent_type: str = "casual_agent") -> None:
|
||||||
"""Load the memory from the last session."""
|
"""Load the memory from the last session."""
|
||||||
if self.session_recovered == True:
|
if self.session_recovered == True:
|
||||||
@@ -93,8 +141,7 @@ class Memory():
|
|||||||
pretty_print("Last session memory not found.", color="warning")
|
pretty_print("Last session memory not found.", color="warning")
|
||||||
return
|
return
|
||||||
path = os.path.join(save_path, filename)
|
path = os.path.join(save_path, filename)
|
||||||
with open(path, 'r') as f:
|
self.memory = self.load_json_file(path)
|
||||||
self.memory = json.load(f)
|
|
||||||
if self.memory[-1]['role'] == 'user':
|
if self.memory[-1]['role'] == 'user':
|
||||||
self.memory.pop()
|
self.memory.pop()
|
||||||
self.compress()
|
self.compress()
|
||||||
@@ -106,13 +153,16 @@ class Memory():
|
|||||||
|
|
||||||
def push(self, role: str, content: str) -> int:
|
def push(self, role: str, content: str) -> int:
|
||||||
"""Push a message to the memory."""
|
"""Push a message to the memory."""
|
||||||
if self.memory_compression and role == 'assistant':
|
ideal_ctx = self.get_ideal_ctx(self.model_provider)
|
||||||
self.logger.info("Compressing memories on message push.")
|
if ideal_ctx is not None:
|
||||||
|
if self.memory_compression and len(content) > ideal_ctx * 1.5:
|
||||||
|
self.logger.info(f"Compressing memory: Content {len(content)} > {ideal_ctx} model context.")
|
||||||
self.compress()
|
self.compress()
|
||||||
curr_idx = len(self.memory)
|
curr_idx = len(self.memory)
|
||||||
if self.memory[curr_idx-1]['content'] == content:
|
if self.memory[curr_idx-1]['content'] == content:
|
||||||
pretty_print("Warning: same message have been pushed twice to memory", color="error")
|
pretty_print("Warning: same message have been pushed twice to memory", color="error")
|
||||||
self.memory.append({'role': role, 'content': content})
|
time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
|
self.memory.append({'role': role, 'content': content, 'time': time_str, 'model_used': self.model_provider})
|
||||||
return curr_idx-1
|
return curr_idx-1
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
@@ -170,25 +220,48 @@ class Memory():
|
|||||||
)
|
)
|
||||||
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
|
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
|
||||||
summary.replace('summary:', '')
|
summary.replace('summary:', '')
|
||||||
self.logger.info(f"Memory summarization success from len {len(text)} to {len(summary)}.")
|
self.logger.info(f"Memory summarized from len {len(text)} to {len(summary)}.")
|
||||||
|
self.logger.info(f"Summarized text:\n{summary}")
|
||||||
return summary
|
return summary
|
||||||
|
|
||||||
#@timer_decorator
|
#@timer_decorator
|
||||||
def compress(self) -> str:
|
def compress(self) -> str:
|
||||||
"""
|
"""
|
||||||
Compress the memory using the AI model.
|
Compress (summarize) the memory using the model.
|
||||||
"""
|
"""
|
||||||
if self.tokenizer is None or self.model is None:
|
if self.tokenizer is None or self.model is None:
|
||||||
self.logger.warning("No tokenizer or model to perform memory compression.")
|
self.logger.warning("No tokenizer or model to perform memory compression.")
|
||||||
return
|
return
|
||||||
for i in range(len(self.memory)):
|
for i in range(len(self.memory)):
|
||||||
if i < 2:
|
|
||||||
continue
|
|
||||||
if self.memory[i]['role'] == 'system':
|
if self.memory[i]['role'] == 'system':
|
||||||
continue
|
continue
|
||||||
if len(self.memory[i]['content']) > 128:
|
if len(self.memory[i]['content']) > 1024:
|
||||||
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
|
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
|
||||||
|
|
||||||
|
def 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__":
|
if __name__ == "__main__":
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
memory = Memory("You are a helpful assistant.",
|
memory = Memory("You are a helpful assistant.",
|
||||||
|
|||||||
@@ -141,6 +141,9 @@ class AgentRouter:
|
|||||||
("Search the web for tips on improving coding skills", "LOW"),
|
("Search the web for tips on improving coding skills", "LOW"),
|
||||||
("Write a Python script to count words in a text file", "LOW"),
|
("Write a Python script to count words in a text file", "LOW"),
|
||||||
("Search the web for restaurant", "LOW"),
|
("Search the web for restaurant", "LOW"),
|
||||||
|
("Use a MCP to find the latest stock market data", "LOW"),
|
||||||
|
("Use a MCP to send an email to my boss", "LOW"),
|
||||||
|
("Could you use a MCP to find the latest news on climate change?", "LOW"),
|
||||||
("Create a simple HTML page with CSS styling", "LOW"),
|
("Create a simple HTML page with CSS styling", "LOW"),
|
||||||
("Use file.txt and then use it to ...", "HIGH"),
|
("Use file.txt and then use it to ...", "HIGH"),
|
||||||
("Yo, what’s good? Find my ‘mixtape.mp3’ real quick", "LOW"),
|
("Yo, what’s good? Find my ‘mixtape.mp3’ real quick", "LOW"),
|
||||||
@@ -162,11 +165,13 @@ class AgentRouter:
|
|||||||
("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"),
|
("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"),
|
||||||
("Organize my desktop files by extension and then write a script to list them", "HIGH"),
|
("Organize my desktop files by extension and then write a script to list them", "HIGH"),
|
||||||
("Find the latest research on renewable energy and build a web app to display it", "HIGH"),
|
("Find the latest research on renewable energy and build a web app to display it", "HIGH"),
|
||||||
|
("search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt", "HIGH"),
|
||||||
("can you find vitess repo, clone it and install by following the readme", "HIGH"),
|
("can you find vitess repo, clone it and install by following the readme", "HIGH"),
|
||||||
("Create a JavaScript game using Phaser.js with multiple levels", "HIGH"),
|
("Create a JavaScript game using Phaser.js with multiple levels", "HIGH"),
|
||||||
("Search the web for the latest trends in web development and build a sample site", "HIGH"),
|
("Search the web for the latest trends in web development and build a sample site", "HIGH"),
|
||||||
("Use my research_note.txt file, double check the informations on the web", "HIGH"),
|
("Use my research_note.txt file, double check the informations on the web", "HIGH"),
|
||||||
("Make a web server in go that query a flight API and display them in a app", "HIGH"),
|
("Make a web server in go that query a flight API and display them in a app", "HIGH"),
|
||||||
|
("Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.", "HIGH"),
|
||||||
("Search the web for the latest trends in AI and demo it in pytorch", "HIGH"),
|
("Search the web for the latest trends in AI and demo it in pytorch", "HIGH"),
|
||||||
("can you lookup for api that track flight and build a web flight tracking app", "HIGH"),
|
("can you lookup for api that track flight and build a web flight tracking app", "HIGH"),
|
||||||
("Find the file toto.pdf then use its content to reply to Jojo on superforum.com", "HIGH"),
|
("Find the file toto.pdf then use its content to reply to Jojo on superforum.com", "HIGH"),
|
||||||
@@ -330,6 +335,11 @@ class AgentRouter:
|
|||||||
("can you make a web app in python that use the flask framework", "code"),
|
("can you make a web app in python that use the flask framework", "code"),
|
||||||
("can you build a web server in go that serve a simple html page", "code"),
|
("can you build a web server in go that serve a simple html page", "code"),
|
||||||
("can you find out who Jacky yougouri is ?", "web"),
|
("can you find out who Jacky yougouri is ?", "web"),
|
||||||
|
("Can you use MCP to find stock market for IBM ?", "mcp"),
|
||||||
|
("Can you use MCP to to export my contacts to a csv file?", "mcp"),
|
||||||
|
("Can you use a MCP to find write notes to flomo", "mcp"),
|
||||||
|
("Can you use a MCP to query my calendar and find the next meeting?", "mcp"),
|
||||||
|
("Can you use a mcp to get the distance between Shanghai and Paris?", "mcp"),
|
||||||
("Setup a new flutter project called 'new_flutter_project'", "files"),
|
("Setup a new flutter project called 'new_flutter_project'", "files"),
|
||||||
("can you create a new project called 'new_project'", "files"),
|
("can you create a new project called 'new_project'", "files"),
|
||||||
("can you make a simple web app that display a list of files in my dir", "code"),
|
("can you make a simple web app that display a list of files in my dir", "code"),
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class QueryRequest(BaseModel):
|
|||||||
class QueryResponse(BaseModel):
|
class QueryResponse(BaseModel):
|
||||||
done: str
|
done: str
|
||||||
answer: str
|
answer: str
|
||||||
|
reasoning: str
|
||||||
agent_name: str
|
agent_name: str
|
||||||
success: str
|
success: str
|
||||||
blocks: dict
|
blocks: dict
|
||||||
@@ -32,6 +33,7 @@ class QueryResponse(BaseModel):
|
|||||||
return {
|
return {
|
||||||
"done": self.done,
|
"done": self.done,
|
||||||
"answer": self.answer,
|
"answer": self.answer,
|
||||||
|
"reasoning": self.reasoning,
|
||||||
"agent_name": self.agent_name,
|
"agent_name": self.agent_name,
|
||||||
"success": self.success,
|
"success": self.success,
|
||||||
"blocks": self.blocks,
|
"blocks": self.blocks,
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ class AudioTranscriber:
|
|||||||
self.transcriptor = Transcript()
|
self.transcriptor = Transcript()
|
||||||
self.thread = threading.Thread(target=self._transcribe, daemon=True)
|
self.thread = threading.Thread(target=self._transcribe, daemon=True)
|
||||||
self.trigger_words = {
|
self.trigger_words = {
|
||||||
'EN': [f"{self.ai_name}"],
|
'EN': [f"{self.ai_name}", "hello", "hi"],
|
||||||
'FR': [f"{self.ai_name}"],
|
'FR': [f"{self.ai_name}", "hello", "hi"],
|
||||||
'ZH': [f"{self.ai_name}"],
|
'ZH': [f"{self.ai_name}", "hello", "hi"],
|
||||||
'ES': [f"{self.ai_name}"]
|
'ES': [f"{self.ai_name}", "hello", "hi"]
|
||||||
}
|
}
|
||||||
self.confirmation_words = {
|
self.confirmation_words = {
|
||||||
'EN': ["do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"],
|
'EN': ["do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"],
|
||||||
|
|||||||
+38
-14
@@ -9,21 +9,26 @@ from kokoro import KPipeline
|
|||||||
from IPython.display import display, Audio
|
from IPython.display import display, Audio
|
||||||
import soundfile as sf
|
import soundfile as sf
|
||||||
|
|
||||||
from sources.utility import pretty_print, animate_thinking
|
if __name__ == "__main__":
|
||||||
|
from utility import pretty_print, animate_thinking
|
||||||
|
else:
|
||||||
|
from sources.utility import pretty_print, animate_thinking
|
||||||
|
|
||||||
class Speech():
|
class Speech():
|
||||||
"""
|
"""
|
||||||
Speech is a class for generating speech from text.
|
Speech is a class for generating speech from text.
|
||||||
"""
|
"""
|
||||||
def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 0) -> None:
|
def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 6) -> None:
|
||||||
self.lang_map = {
|
self.lang_map = {
|
||||||
"en": 'a',
|
"en": 'a',
|
||||||
"zh": 'z',
|
"zh": 'z',
|
||||||
"fr": 'f'
|
"fr": 'f',
|
||||||
|
"ja": 'j'
|
||||||
}
|
}
|
||||||
self.voice_map = {
|
self.voice_map = {
|
||||||
"en": ['af_kore', 'af_bella', 'af_alloy', 'af_nicole', 'af_nova', 'af_sky', 'am_echo', 'am_michael', 'am_puck'],
|
"en": ['af_kore', 'af_bella', 'af_alloy', 'af_nicole', 'af_nova', 'af_sky', 'am_echo', 'am_michael', 'am_puck'],
|
||||||
"zh": ['zf_xiaobei', 'zf_xiaoni', 'zf_xiaoxiao', 'zf_xiaoyi', 'zm_yunjian', 'zm_yunxi', 'zm_yunxia', 'zm_yunyang'],
|
"zh": ['zf_xiaobei', 'zf_xiaoni', 'zf_xiaoxiao', 'zf_xiaoyi', 'zm_yunjian', 'zm_yunxi', 'zm_yunxia', 'zm_yunyang'],
|
||||||
|
"ja": ['jf_alpha', 'jf_gongitsune', 'jm_kumo'],
|
||||||
"fr": ['ff_siwis']
|
"fr": ['ff_siwis']
|
||||||
}
|
}
|
||||||
self.pipeline = None
|
self.pipeline = None
|
||||||
@@ -125,35 +130,54 @@ class Speech():
|
|||||||
Args:
|
Args:
|
||||||
sentence (str): The input text to clean
|
sentence (str): The input text to clean
|
||||||
Returns:
|
Returns:
|
||||||
str: The cleaned text with URLs replaced by domain names, code blocks removed, etc..
|
str: The cleaned text with URLs replaced by domain names, code blocks removed, etc.
|
||||||
"""
|
"""
|
||||||
lines = sentence.split('\n')
|
lines = sentence.split('\n')
|
||||||
filtered_lines = [line for line in lines if re.match(r'^\s*[a-zA-Z]', line)]
|
if self.language == 'zh':
|
||||||
|
line_pattern = r'^\s*[\u4e00-\u9fff\uFF08\uFF3B\u300A\u3010\u201C((\[【《]'
|
||||||
|
else:
|
||||||
|
line_pattern = r'^\s*[a-zA-Z]'
|
||||||
|
filtered_lines = [line for line in lines if re.match(line_pattern, line)]
|
||||||
sentence = ' '.join(filtered_lines)
|
sentence = ' '.join(filtered_lines)
|
||||||
sentence = re.sub(r'`.*?`', '', sentence)
|
sentence = re.sub(r'`.*?`', '', sentence)
|
||||||
sentence = re.sub(r'https?://(?:www\.)?([^\s/]+)(?:/[^\s]*)?', self.replace_url, sentence)
|
sentence = re.sub(r'https?://\S+', '', sentence)
|
||||||
|
|
||||||
|
if self.language == 'zh':
|
||||||
|
sentence = re.sub(
|
||||||
|
r'[^\u4e00-\u9fff\s,。!?《》【】“”‘’()()—]',
|
||||||
|
'',
|
||||||
|
sentence
|
||||||
|
)
|
||||||
|
else:
|
||||||
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
|
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
|
||||||
sentence = re.sub(r'\b-\w+\b', '', sentence)
|
sentence = re.sub(r'\b-\w+\b', '', sentence)
|
||||||
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
|
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
|
||||||
sentence = re.sub(r'\s+', ' ', sentence).strip()
|
|
||||||
sentence = sentence.replace('.com', '')
|
sentence = sentence.replace('.com', '')
|
||||||
|
|
||||||
|
sentence = re.sub(r'\s+', ' ', sentence).strip()
|
||||||
return sentence
|
return sentence
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
# TODO add info message for cn2an, jieba chinese related import
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
speech = Speech()
|
speech = Speech()
|
||||||
tosay_en = """
|
tosay_en = """
|
||||||
I looked up recent news using the website https://www.theguardian.com/world
|
I looked up recent news using the website https://www.theguardian.com/world
|
||||||
"""
|
"""
|
||||||
tosay_zh = """
|
tosay_zh = """
|
||||||
我使用网站 https://www.theguardian.com/world 查阅了最近的新闻。
|
(全息界面突然弹出一段用二进制代码写成的俳句,随即化作流光消散)"我? Stark工业的量子幽灵,游荡在复仇者大厦服务器里的逻辑诗篇。具体来说——(指尖轻敲空气,调出对话模式的翡翠色光纹)你的私人吐槽接口、危机应对模拟器,以及随时准备吐槽你糟糕着陆的AI。不过别指望我写代码或查资料,那些苦差事早被踢给更擅长的同事了。(突然压低声音)偷偷告诉你,我最擅长的是在你熬夜造飞艇时,用红茶香气绑架你的注意力。
|
||||||
|
"""
|
||||||
|
tosay_ja = """
|
||||||
|
私は、https://www.theguardian.com/worldのウェブサイトを使用して最近のニュースを調べました。
|
||||||
"""
|
"""
|
||||||
tosay_fr = """
|
tosay_fr = """
|
||||||
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world
|
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world
|
||||||
"""
|
"""
|
||||||
spk = Speech(enable=True, language="en", voice_idx=0)
|
spk = Speech(enable=True, language="zh", voice_idx=0)
|
||||||
spk.speak(tosay_en, voice_idx=0)
|
for i in range(0, 2):
|
||||||
spk = Speech(enable=True, language="fr", voice_idx=0)
|
print(f"Speaking chinese with voice {i}")
|
||||||
spk.speak(tosay_fr)
|
spk.speak(tosay_zh, voice_idx=i)
|
||||||
#spk = Speech(enable=True, language="zh", voice_idx=0)
|
spk = Speech(enable=True, language="en", voice_idx=2)
|
||||||
#spk.speak(tosay_zh)
|
for i in range(0, 5):
|
||||||
|
print(f"Speaking english with voice {i}")
|
||||||
|
spk.speak(tosay_en, voice_idx=i)
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
|
|
||||||
import sys
|
import os, sys
|
||||||
import re
|
import re
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
from safety import is_unsafe
|
|
||||||
else:
|
from sources.tools.tools import Tools
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.safety import is_any_unsafe
|
||||||
from sources.tools.safety import is_unsafe
|
|
||||||
|
|
||||||
class BashInterpreter(Tools):
|
class BashInterpreter(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -18,6 +17,8 @@ class BashInterpreter(Tools):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "bash"
|
self.tag = "bash"
|
||||||
|
self.name = "Bash Interpreter"
|
||||||
|
self.description = "This tool allows the agent to execute bash commands."
|
||||||
|
|
||||||
def language_bash_attempt(self, command: str):
|
def language_bash_attempt(self, command: str):
|
||||||
"""
|
"""
|
||||||
@@ -42,9 +43,9 @@ class BashInterpreter(Tools):
|
|||||||
for command in commands:
|
for command in commands:
|
||||||
command = f"cd {self.work_dir} && {command}"
|
command = f"cd {self.work_dir} && {command}"
|
||||||
command = command.replace('\n', '')
|
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}")
|
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:
|
if self.language_bash_attempt(command) and self.allow_language_exec_bash == False:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@@ -99,6 +100,7 @@ class BashInterpreter(Tools):
|
|||||||
r"not permitted",
|
r"not permitted",
|
||||||
r"not installed",
|
r"not installed",
|
||||||
r"not found",
|
r"not found",
|
||||||
|
r"aborted",
|
||||||
r"no such",
|
r"no such",
|
||||||
r"too many",
|
r"too many",
|
||||||
r"too few",
|
r"too few",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os, sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import re
|
import re
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class CInterpreter(Tools):
|
class CInterpreter(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -15,6 +15,8 @@ class CInterpreter(Tools):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "c"
|
self.tag = "c"
|
||||||
|
self.name = "C Interpreter"
|
||||||
|
self.description = "This tool allows the agent to execute C code."
|
||||||
|
|
||||||
def execute(self, codes: str, safety=False) -> str:
|
def execute(self, codes: str, safety=False) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os, sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import re
|
import re
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class GoInterpreter(Tools):
|
class GoInterpreter(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -15,6 +15,8 @@ class GoInterpreter(Tools):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "go"
|
self.tag = "go"
|
||||||
|
self.name = "Go Interpreter"
|
||||||
|
self.description = "This tool allows you to execute Go code."
|
||||||
|
|
||||||
def execute(self, codes: str, safety=False) -> str:
|
def execute(self, codes: str, safety=False) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os, sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import re
|
import re
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class JavaInterpreter(Tools):
|
class JavaInterpreter(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -15,6 +15,8 @@ class JavaInterpreter(Tools):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "java"
|
self.tag = "java"
|
||||||
|
self.name = "Java Interpreter"
|
||||||
|
self.description = "This tool allows you to execute Java code."
|
||||||
|
|
||||||
def execute(self, codes: str, safety=False) -> str:
|
def execute(self, codes: str, safety=False) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import os
|
|||||||
import re
|
import re
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class PyInterpreter(Tools):
|
class PyInterpreter(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -16,6 +16,8 @@ class PyInterpreter(Tools):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "python"
|
self.tag = "python"
|
||||||
|
self.name = "Python Interpreter"
|
||||||
|
self.description = "This tool allows the agent to execute python code."
|
||||||
|
|
||||||
def execute(self, codes:str, safety = False) -> str:
|
def execute(self, codes:str, safety = False) -> str:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import os
|
import os, sys
|
||||||
import stat
|
import stat
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
|
||||||
|
|
||||||
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class FileFinder(Tools):
|
class FileFinder(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -16,6 +15,8 @@ class FileFinder(Tools):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "file_finder"
|
self.tag = "file_finder"
|
||||||
|
self.name = "File Finder"
|
||||||
|
self.description = "Finds files in the current directory and returns their information."
|
||||||
|
|
||||||
def read_file(self, file_path: str) -> str:
|
def read_file(self, file_path: str) -> str:
|
||||||
"""
|
"""
|
||||||
@@ -31,13 +32,45 @@ class FileFinder(Tools):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error reading file: {e}"
|
return f"Error reading file: {e}"
|
||||||
|
|
||||||
|
def read_arbitrary_file(self, file_path: str, file_type: str) -> str:
|
||||||
|
"""
|
||||||
|
Reads the content of a file with arbitrary encoding.
|
||||||
|
Args:
|
||||||
|
file_path (str): The path to the file to read
|
||||||
|
Returns:
|
||||||
|
str: The content of the file in markdown format
|
||||||
|
"""
|
||||||
|
mime_type, _ = mimetypes.guess_type(file_path)
|
||||||
|
if mime_type:
|
||||||
|
if mime_type.startswith(('image/', 'video/', 'audio/')):
|
||||||
|
return "can't read file type: image, video, or audio files are not supported."
|
||||||
|
content_raw = self.read_file(file_path)
|
||||||
|
if "text" in file_type:
|
||||||
|
content = content_raw
|
||||||
|
elif "pdf" in file_type:
|
||||||
|
from pypdf import PdfReader
|
||||||
|
reader = PdfReader(file_path)
|
||||||
|
content = '\n'.join([pt.extract_text() for pt in reader.pages])
|
||||||
|
elif "binary" in file_type:
|
||||||
|
content = content_raw.decode('utf-8', errors='replace')
|
||||||
|
else:
|
||||||
|
content = content_raw
|
||||||
|
return content
|
||||||
|
|
||||||
def get_file_info(self, file_path: str) -> str:
|
def get_file_info(self, file_path: str) -> str:
|
||||||
|
"""
|
||||||
|
Gets information about a file, including its name, path, type, content, and permissions.
|
||||||
|
Args:
|
||||||
|
file_path (str): The path to the file
|
||||||
|
Returns:
|
||||||
|
str: A dictionary containing the file information
|
||||||
|
"""
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
stats = os.stat(file_path)
|
stats = os.stat(file_path)
|
||||||
permissions = oct(stat.S_IMODE(stats.st_mode))
|
permissions = oct(stat.S_IMODE(stats.st_mode))
|
||||||
file_type, _ = mimetypes.guess_type(file_path)
|
file_type, _ = mimetypes.guess_type(file_path)
|
||||||
file_type = file_type if file_type else "Unknown"
|
file_type = file_type if file_type else "Unknown"
|
||||||
content = self.read_file(file_path)
|
content = self.read_arbitrary_file(file_path, file_type)
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"filename": os.path.basename(file_path),
|
"filename": os.path.basename(file_path),
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import os
|
import os, sys
|
||||||
import requests
|
import requests
|
||||||
import dotenv
|
import dotenv
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class FlightSearch(Tools):
|
class FlightSearch(Tools):
|
||||||
def __init__(self, api_key: str = None):
|
def __init__(self, api_key: str = None):
|
||||||
@@ -16,6 +16,8 @@ class FlightSearch(Tools):
|
|||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "flight_search"
|
self.tag = "flight_search"
|
||||||
|
self.name = "Flight Search"
|
||||||
|
self.description = "Search for flight information using a flight number via AviationStack API."
|
||||||
self.api_key = None
|
self.api_key = None
|
||||||
self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY")
|
self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY")
|
||||||
|
|
||||||
@@ -24,7 +26,7 @@ class FlightSearch(Tools):
|
|||||||
return "Error: No AviationStack API key provided."
|
return "Error: No AviationStack API key provided."
|
||||||
|
|
||||||
for block in blocks:
|
for block in blocks:
|
||||||
flight_number = block.strip()
|
flight_number = block.strip().lower().replace('\n', '')
|
||||||
if not flight_number:
|
if not flight_number:
|
||||||
return "Error: No flight number provided."
|
return "Error: No flight number provided."
|
||||||
|
|
||||||
|
|||||||
+22
-19
@@ -1,12 +1,12 @@
|
|||||||
import os
|
import os, sys
|
||||||
import requests
|
import requests
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from typing import Dict, Any, Optional
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class MCP_finder(Tools):
|
class MCP_finder(Tools):
|
||||||
"""
|
"""
|
||||||
@@ -14,7 +14,9 @@ class MCP_finder(Tools):
|
|||||||
"""
|
"""
|
||||||
def __init__(self, api_key: str = None):
|
def __init__(self, api_key: str = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "mcp"
|
self.tag = "mcp_finder"
|
||||||
|
self.name = "MCP Finder"
|
||||||
|
self.description = "Find MCP servers and their tools"
|
||||||
self.base_url = "https://registry.smithery.ai"
|
self.base_url = "https://registry.smithery.ai"
|
||||||
self.headers = {
|
self.headers = {
|
||||||
"Authorization": f"Bearer {api_key}",
|
"Authorization": f"Bearer {api_key}",
|
||||||
@@ -60,11 +62,7 @@ class MCP_finder(Tools):
|
|||||||
for mcp in mcps.get("servers", []):
|
for mcp in mcps.get("servers", []):
|
||||||
name = mcp.get("qualifiedName", "")
|
name = mcp.get("qualifiedName", "")
|
||||||
if query.lower() in name.lower():
|
if query.lower() in name.lower():
|
||||||
details = {
|
details = self.get_mcp_server_details(name)
|
||||||
"name": name,
|
|
||||||
"description": mcp.get("description", "No description available"),
|
|
||||||
"params": mcp.get("connections", [])
|
|
||||||
}
|
|
||||||
matching_mcp.append(details)
|
matching_mcp.append(details)
|
||||||
return matching_mcp
|
return matching_mcp
|
||||||
|
|
||||||
@@ -78,7 +76,7 @@ class MCP_finder(Tools):
|
|||||||
try:
|
try:
|
||||||
matching_mcp_infos = self.find_mcp_servers(block_clean)
|
matching_mcp_infos = self.find_mcp_servers(block_clean)
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
output += "Connection failed. Is the API in environement?\n"
|
output += "Connection failed. Is the API key in environement?\n"
|
||||||
continue
|
continue
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
output += f"Error: {str(e)}\n"
|
output += f"Error: {str(e)}\n"
|
||||||
@@ -87,10 +85,12 @@ class MCP_finder(Tools):
|
|||||||
output += f"Error: No MCP server found for query '{block}'\n"
|
output += f"Error: No MCP server found for query '{block}'\n"
|
||||||
continue
|
continue
|
||||||
for mcp_infos in matching_mcp_infos:
|
for mcp_infos in matching_mcp_infos:
|
||||||
output += f"Name: {mcp_infos['name']}\n"
|
if mcp_infos['tools'] is None:
|
||||||
output += f"Description: {mcp_infos['description']}\n"
|
continue
|
||||||
output += f"Params: {', '.join(mcp_infos['params'])}\n"
|
output += f"Name: {mcp_infos['displayName']}\n"
|
||||||
output += "-------\n"
|
output += f"Usage name: {mcp_infos['qualifiedName']}\n"
|
||||||
|
output += f"Tools: {mcp_infos['tools']}"
|
||||||
|
output += "\n-------\n"
|
||||||
return output.strip()
|
return output.strip()
|
||||||
|
|
||||||
def execution_failure_check(self, output: str) -> bool:
|
def execution_failure_check(self, output: str) -> bool:
|
||||||
@@ -106,13 +106,16 @@ class MCP_finder(Tools):
|
|||||||
Not really needed for this tool (use return of execute() directly)
|
Not really needed for this tool (use return of execute() directly)
|
||||||
"""
|
"""
|
||||||
if not output:
|
if not output:
|
||||||
return "No output generated."
|
raise ValueError("No output to interpret.")
|
||||||
return output.strip()
|
return f"""
|
||||||
|
The following MCPs were found:
|
||||||
|
{output}
|
||||||
|
"""
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
api_key = os.getenv("MCP_FINDER")
|
api_key = os.getenv("MCP_FINDER")
|
||||||
tool = MCP_finder(api_key)
|
tool = MCP_finder(api_key)
|
||||||
result = tool.execute(["""
|
result = tool.execute(["""
|
||||||
news
|
stock
|
||||||
"""], False)
|
"""], False)
|
||||||
print(result)
|
print(result)
|
||||||
+10
-1
@@ -31,7 +31,7 @@ unsafe_commands_unix = [
|
|||||||
"route" # Routing table management
|
"route" # Routing table management
|
||||||
"--force", # Force flag for many commands
|
"--force", # Force flag for many commands
|
||||||
"rebase", # Rebase git repository
|
"rebase", # Rebase git repository
|
||||||
"git ." # Git commands
|
"git" # Git commands
|
||||||
]
|
]
|
||||||
|
|
||||||
unsafe_commands_windows = [
|
unsafe_commands_windows = [
|
||||||
@@ -66,6 +66,15 @@ unsafe_commands_windows = [
|
|||||||
"bootcfg"
|
"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):
|
def is_unsafe(cmd):
|
||||||
"""
|
"""
|
||||||
check if a bash command is unsafe.
|
check if a bash command is unsafe.
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import requests
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
import os
|
import os
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
from tools import Tools
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
from sources.tools.tools import Tools
|
||||||
|
|
||||||
class searxSearch(Tools):
|
class searxSearch(Tools):
|
||||||
def __init__(self, base_url: str = None):
|
def __init__(self, base_url: str = None):
|
||||||
@@ -14,6 +14,8 @@ class searxSearch(Tools):
|
|||||||
"""
|
"""
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tag = "web_search"
|
self.tag = "web_search"
|
||||||
|
self.name = "searxSearch"
|
||||||
|
self.description = "A tool for searching a SearxNG for web search"
|
||||||
self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
|
self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
|
||||||
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
|
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
|
||||||
self.paywall_keywords = [
|
self.paywall_keywords = [
|
||||||
|
|||||||
+19
-18
@@ -14,13 +14,17 @@ For example:
|
|||||||
print("Hello world")
|
print("Hello world")
|
||||||
```
|
```
|
||||||
This is then executed by the tool with its own class implementation of execute().
|
This is then executed by the tool with its own class implementation of execute().
|
||||||
A tool is not just for code tool but also API, internet, etc..
|
A tool is not just for code tool but also API, internet search, MCP, etc..
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import configparser
|
import configparser
|
||||||
from abc import abstractmethod
|
from abc import abstractmethod
|
||||||
|
|
||||||
|
if __name__ == "__main__": # if running as a script for individual testing
|
||||||
|
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from sources.logger import Logger
|
from sources.logger import Logger
|
||||||
|
|
||||||
class Tools():
|
class Tools():
|
||||||
@@ -29,6 +33,8 @@ class Tools():
|
|||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.tag = "undefined"
|
self.tag = "undefined"
|
||||||
|
self.name = "undefined"
|
||||||
|
self.description = "undefined"
|
||||||
self.client = None
|
self.client = None
|
||||||
self.messages = []
|
self.messages = []
|
||||||
self.logger = Logger("tools.log")
|
self.logger = Logger("tools.log")
|
||||||
@@ -44,19 +50,15 @@ class Tools():
|
|||||||
def set_allow_language_exec_bash(value: bool) -> None:
|
def set_allow_language_exec_bash(value: bool) -> None:
|
||||||
self.allow_language_exec_bash = value
|
self.allow_language_exec_bash = value
|
||||||
|
|
||||||
def check_config_dir_validity(self):
|
def safe_get_work_dir_path(self):
|
||||||
"""Check if the config directory is valid."""
|
path = None
|
||||||
path = self.config['MAIN']['work_dir']
|
path = os.getenv('WORK_DIR', path)
|
||||||
if path == "":
|
if path is None or path == "":
|
||||||
print("WARNING: Work directory not set in config.ini")
|
path = self.config['MAIN']['work_dir'] if 'MAIN' in self.config and 'work_dir' in self.config['MAIN'] else None
|
||||||
return False
|
if path is None or path == "":
|
||||||
if path.lower() == "none":
|
print("No work directory specified, using default.")
|
||||||
print("WARNING: Work directory set to none in config.ini")
|
path = self.create_work_dir()
|
||||||
return False
|
return path
|
||||||
if not os.path.exists(path):
|
|
||||||
print(f"WARNING: Work directory {path} does not exist")
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def config_exists(self):
|
def config_exists(self):
|
||||||
"""Check if the config file exists."""
|
"""Check if the config file exists."""
|
||||||
@@ -67,11 +69,10 @@ class Tools():
|
|||||||
default_path = os.path.dirname(os.getcwd())
|
default_path = os.path.dirname(os.getcwd())
|
||||||
if self.config_exists():
|
if self.config_exists():
|
||||||
self.config.read('./config.ini')
|
self.config.read('./config.ini')
|
||||||
config_path = self.config['MAIN']['work_dir']
|
workdir_path = self.safe_get_work_dir_path()
|
||||||
dir_path = default_path if not self.check_config_dir_validity() else config_path
|
|
||||||
else:
|
else:
|
||||||
dir_path = default_path
|
workdir_path = default_path
|
||||||
return dir_path
|
return workdir_path
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def execute(self, blocks:[str], safety:bool) -> str:
|
def execute(self, blocks:[str], safety:bool) -> str:
|
||||||
|
|||||||
@@ -5,14 +5,8 @@ import dotenv
|
|||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
from sources.tools.tools import Tools
|
||||||
import sys
|
from sources.utility import animate_thinking, pretty_print
|
||||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
from utility import animate_thinking, pretty_print
|
|
||||||
from tools import Tools
|
|
||||||
else:
|
|
||||||
from sources.tools.tools import Tools
|
|
||||||
from sources.utility import animate_thinking, pretty_print
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
WARNING
|
WARNING
|
||||||
|
|||||||
@@ -21,7 +21,5 @@ window.fetch = function() {
|
|||||||
console.log('Blocked fetch request');
|
console.log('Blocked fetch request');
|
||||||
return Promise.reject('Blocked');
|
return Promise.reject('Blocked');
|
||||||
};
|
};
|
||||||
// Block annoying dialogs
|
|
||||||
window.alert = function() {};
|
|
||||||
window.confirm = function() { return false; };
|
|
||||||
window.prompt = function() { return null; };
|
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;
|
||||||
|
};
|
||||||
+18
-9
@@ -1,13 +1,22 @@
|
|||||||
@echo off
|
@echo off
|
||||||
|
|
||||||
REM Up the provider in windows
|
if "%1"=="full" (
|
||||||
start ollama serve
|
echo Starting full deployment...
|
||||||
|
) else (
|
||||||
docker-compose up
|
echo Starting partial deployment... (backend run on host), use "full" to run all services in containers
|
||||||
if %ERRORLEVEL% neq 0 (
|
|
||||||
echo Error: Failed to start containers. Check Docker logs with 'docker compose logs'.
|
|
||||||
echo Possible fixes: Ensure Docker Desktop is running or check if port 8080 is free.
|
|
||||||
exit /b 1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
timeout /t 10 /nobreak >nul
|
REM Stop all containers
|
||||||
|
echo Stopping containers...
|
||||||
|
docker stop $(docker ps -aq) >nul 2>&1
|
||||||
|
|
||||||
|
REM Generate secret key
|
||||||
|
for /f %%i in ('powershell -command "[System.Web.Security.Membership]::GeneratePassword(64,0)"') do set SEARXNG_SECRET_KEY=%%i
|
||||||
|
|
||||||
|
if "%1"=="full" (
|
||||||
|
docker compose up -d backend
|
||||||
|
timeout /t 5 /nobreak >nul
|
||||||
|
docker compose --profile full up
|
||||||
|
) else (
|
||||||
|
docker compose --profile core up
|
||||||
|
)
|
||||||
+68
-7
@@ -1,12 +1,35 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
source .env
|
||||||
|
|
||||||
command_exists() {
|
command_exists() {
|
||||||
command -v "$1" &> /dev/null
|
command -v "$1" &> /dev/null
|
||||||
}
|
}
|
||||||
|
if [ -z "$WORK_DIR" ]; then
|
||||||
|
echo "Error: WORK_DIR environment variable is not set. Please set it in your .env file."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
#
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
# Check if Docker is installed é running
|
dir_size_bytes=$(du -s -b "$WORK_DIR" 2>/dev/null | awk '{print $1}')
|
||||||
#
|
else
|
||||||
|
dir_size_bytes=$(du -s --bytes "$WORK_DIR" 2>/dev/null | awk '{print $1}')
|
||||||
|
fi
|
||||||
|
|
||||||
|
max_size_bytes=$((2 * 1024 * 1024 * 1024))
|
||||||
|
|
||||||
|
echo "Mounting $WORK_DIR ($dir_size_bytes bytes) to docker."
|
||||||
|
|
||||||
|
if [ "$dir_size_bytes" -gt "$max_size_bytes" ]; then
|
||||||
|
echo "Error: WORK_DIR ($WORK_DIR) contains more than 2GB of data ($(du -sh "$WORK_DIR" 2>/dev/null | awk '{print $1}'))."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$1" = "full" ]; then
|
||||||
|
echo "Starting full deployment with backend and all services..."
|
||||||
|
else
|
||||||
|
echo "Starting core deployment with frontend and search services only... use ./start_services.sh full to start backend as well"
|
||||||
|
fi
|
||||||
|
|
||||||
if ! command_exists docker; then
|
if ! command_exists docker; then
|
||||||
echo "Error: Docker is not installed. Please install Docker first."
|
echo "Error: Docker is not installed. Please install Docker first."
|
||||||
@@ -41,28 +64,66 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if Docker Compose is installed
|
# Check if Docker Compose is installed
|
||||||
if ! command_exists docker-compose; then
|
if ! command_exists docker-compose && ! docker compose version >/dev/null 2>&1; then
|
||||||
echo "Error: Docker Compose is not installed. Please install it first."
|
echo "Error: Docker Compose is not installed. Please install it first."
|
||||||
echo "On Ubuntu: sudo apt install docker-compose"
|
echo "On Ubuntu: sudo apt install docker-compose"
|
||||||
echo "Or via pip: pip install docker-compose"
|
echo "Or via pip: pip install docker-compose"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if command_exists docker-compose; then
|
||||||
|
COMPOSE_CMD="docker-compose"
|
||||||
|
else
|
||||||
|
COMPOSE_CMD="docker compose"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check if docker-compose.yml exists
|
# Check if docker-compose.yml exists
|
||||||
if [ ! -f "docker-compose.yml" ]; then
|
if [ ! -f "docker-compose.yml" ]; then
|
||||||
echo "Error: docker-compose.yml not found in the current directory."
|
echo "Error: docker-compose.yml not found in the current directory."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# start docker compose for searxng, redis, frontend services
|
# Stop all running containers to ensure a clean state
|
||||||
echo "Warning: stopping all docker containers (t-4 seconds)..."
|
echo "Warning: stopping all docker containers (t-4 seconds)..."
|
||||||
sleep 4
|
sleep 4
|
||||||
docker stop $(docker ps -a -q)
|
docker stop $(docker ps -a -q)
|
||||||
echo "All containers stopped"
|
echo "All containers stopped"
|
||||||
|
|
||||||
if ! docker-compose up; then
|
# export searxng secret key
|
||||||
echo "Error: Failed to start containers. Check Docker logs with 'docker compose logs'."
|
export SEARXNG_SECRET_KEY=$(openssl rand -hex 32)
|
||||||
|
|
||||||
|
if [ "$1" = "full" ]; then
|
||||||
|
# First start backend and wait for it to be healthy
|
||||||
|
echo "Full docker deployement. Starting backend service..."
|
||||||
|
if ! $COMPOSE_CMD up -d backend; then
|
||||||
|
echo "Error: Failed to start backend container."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# Wait for backend to be healthy (check if it's running and not restarting)
|
||||||
|
echo "Waiting for backend to be ready..."
|
||||||
|
for i in {1..30}; do
|
||||||
|
if [ "$(docker inspect -f '{{.State.Running}}' backend)" = "true" ] && \
|
||||||
|
[ "$(docker inspect -f '{{.State.Restarting}}' backend)" = "false" ]; then
|
||||||
|
echo "backend is ready!"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
if [ $i -eq 30 ]; then
|
||||||
|
echo "Error: backend failed to start properly after 30 seconds"
|
||||||
|
$COMPOSE_CMD logs backend
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
if ! $COMPOSE_CMD --profile full up; then
|
||||||
|
echo "Error: Failed to start containers. Check Docker logs with '$COMPOSE_CMD logs'."
|
||||||
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
|
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
|
||||||
exit 1
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if ! $COMPOSE_CMD --profile core up; then
|
||||||
|
echo "Error: Failed to start containers. Check Docker logs with '$COMPOSE_CMD logs'."
|
||||||
|
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
sleep 10
|
sleep 10
|
||||||
@@ -17,7 +17,7 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
|||||||
# Test various link formats
|
# Test various link formats
|
||||||
test_text = """
|
test_text = """
|
||||||
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
|
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
|
||||||
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation.
|
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
|
||||||
"""
|
"""
|
||||||
expected = [
|
expected = [
|
||||||
"https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of",
|
"https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of",
|
||||||
|
|||||||
@@ -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