Author SHA1 Message Date
martin legrand c81c0ffde6 requirement correction 2025-06-01 16:58:16 +02:00
martin legrand 444e7bce22 refactor : remove ntlk import 2025-06-01 16:47:05 +02:00
martin legrand d3f20819ff feat : working mount of work directory on docker 2025-06-01 16:44:07 +02:00
martin legrand 9f0fdd547e refactor: remove unsused sentiment analysis 2025-06-01 16:40:53 +02:00
martin legrand be1bfc5cf2 docker deploy of backend now working 2025-05-31 22:01:42 +02:00
martin legrand a3b0bb22aa latest attempt of dockerization 2025-05-31 19:44:58 +02:00
martin legrand fc74d4361a latest attempt of dockerization 2025-05-31 19:31:25 +02:00
martin legrand 54cc2a03ec feat : updating with latest docker backend build thx to #265 2025-05-31 17:20:40 +02:00
martin legrand b96e83dbbe feat : latest docker attempt + fix attempt for #249 2025-05-29 21:33:48 +02:00
martin legrand eadcfb66d1 merge 2025-05-29 15:37:32 +02:00
martin legrand 1c4a550c6f docker: latest backend dockerization attempt but crash 2025-05-29 15:35:00 +02:00
martin legrand 95aeaf74fa docker: latest backend dockerization attempt but crash 2025-05-29 15:34:23 +02:00
martin legrand ec1f7d31fb update start_servicees.sh 2025-05-29 10:51:08 +08:00
martin legrand abae98cf77 feat : optional run backend on host for start_services.sh 2025-05-29 10:51:08 +08:00
martin legrand 819a3fb98d remove commented service 2025-05-29 10:51:08 +08:00
martin legrand 7d74a348c9 comment out bundle approach 2025-05-29 10:51:08 +08:00
martin legrand a3ad635728 deploy : current attempt at backend dockerization 2025-05-29 10:51:08 +08:00
martin legrand 58656ab43c fix typo in readme 2025-05-28 18:41:47 +02:00
Martin c1a1e9409d Merge pull request #234 from hung-ngm/test-tools-parsing
Test tools parsing
2025-05-28 08:42:18 +02:00
Hung Nguyen 41fe95fcb1 Refine test_tools_parsing 2025-05-28 11:47:21 +10:00
Hung Nguyen c8bccc2395 Added unit tests for tools parsing 2025-05-28 11:44:00 +10:00
Martin e45fa86cda Merge pull request #231 from lckdl/main
fix issue #230
2025-05-27 21:48:30 +02:00
lck c8df9e759c fix: handle missing </think> tag in remove_reasoning_text 2025-05-28 02:26:41 +08:00
Martin b3672c60c0 Merge pull request #229 from Fosowl/dev
update readme
2025-05-27 19:35:08 +02:00
martin legrand 97460ded48 set config.ini back like before 2025-05-27 19:31:28 +02:00
martin legrand 9a34ff3646 set config.ini back like before 2025-05-27 19:30:26 +02:00
martin legrand a53842b8b7 update readme disclaimer 2025-05-27 19:29:03 +02:00
Martin 0a8d898e0b Merge pull request #224 from manra399/feature/added-docker-ignore
Added Docker Ignore file.
2025-05-27 18:50:51 +02:00
martin legrand 92f721886c idk 2025-05-27 18:21:31 +02:00
martin legrand 58f46d4351 update start_servicees.sh 2025-05-27 18:19:20 +02:00
manra399 ee6687df85 Added Docker Ignore file. 2025-05-27 13:55:52 +01:00
Martin 8d15546771 Merge pull request #212 from ifurther/patch-1
Update README_CHT.md
2025-05-26 18:48:14 +02:00
ifurther 7ad084b27f updat readme 2025-05-26 21:20:17 +08:00
Further 63cd5eddd7 Update README_CHT.md 2025-05-26 20:48:22 +08:00
martin legrand 50f9e11a35 feat : optional run backend on host for start_services.sh 2025-05-25 22:34:13 +02:00
martin legrand 500605d5da remove commented service 2025-05-25 21:57:56 +02:00
martin legrand 6ec9647d19 comment out bundle approach 2025-05-25 21:56:29 +02:00
martin legrand 16b8f1a451 deploy : current attempt at backend dockerization 2025-05-25 21:19:18 +02:00
martin legrand 6d053cc3a5 merge with #191 2025-05-25 15:39:23 +02:00
Martin 27b4aaa5e9 Merge pull request #191 from klimentij/feature/openrouter-provider
Openrouter provider
2025-05-25 15:18:57 +02:00
Klimentiy Bulygin 3c19f26792 Merge branch 'main' into feature/openrouter-provider 2025-05-25 13:15:56 +02:00
Klimentij Bulygin 12eec50e1c return original docker-compose 2025-05-25 13:14:10 +02:00
Klimentij Bulygin cd78cb36a0 minor 2025-05-25 13:13:03 +02:00
Klimentij Bulygin ec8cab2d6b Merge branch 'feature/openrouter-provider' of https://github.com/klimentij/agenticSeek into feature/openrouter-provider 2025-05-25 13:12:33 +02:00
Klimentij Bulygin b1f9375115 OpenRouter in .env.example 2025-05-25 13:11:10 +02:00
martin legrand 758faf6285 readme update & refactor llm_provider 2025-05-25 11:45:18 +02:00
Martin 20457a574a Merge pull request #197 from Fosowl/dev
Expandable view of reasoning above message instead of a separate view
2025-05-24 19:15:14 +02:00
martin legrand d375359582 feat : better view of reasoning 2025-05-24 12:45:10 +02:00
Klimentiy Bulygin f738fc732e Update config.ini 2025-05-24 00:16:16 +02:00
Klimentiy Bulygin 3be07e3dcb Update config.ini 2025-05-24 00:15:36 +02:00
Martin 7d252ad422 Merge pull request #190 from Fosowl/dev
Add stop button, reasoning view, add anthropic provider
2025-05-21 16:40:08 +02:00
Klimentij Bulygin 1c73ef141c Restore original config.ini settings 2025-05-20 22:47:09 +02:00
Klimentij Bulygin e3757f54ac Undo OpenRouter related changes to README files 2025-05-20 22:46:56 +02:00
Klimentij Bulygin 12afae7472 Fix: Correct provider_name parsing in config.ini 2025-05-20 19:58:59 +02:00
Klimentij Bulygin 9cbf62b47d Configure OpenRouter as default and verify API key handling 2025-05-20 17:50:41 +02:00
Klimentij Bulygin 51b0ca54c7 Initial commit for OpenRouter provider 2025-05-20 17:47:40 +02:00
martin legrand 0f116cc3d0 rm png file 2025-05-20 16:57:46 +02:00
martin legrand a19ef5df66 rm : test code 2025-05-20 16:56:13 +02:00
martin legrand 195b4a07a9 fix : safety of bash interpreter 2025-05-20 16:53:04 +02:00
martin legrand e3d01083d9 feat : stop button integration 2025-05-20 16:47:35 +02:00
martin legrand 102dc60efb feat : integrate reasoning view 2025-05-17 22:04:07 +02:00
Martin ca2b05b35e Merge pull request #188 from Fosowl/dev
Better browser fingerprint spoofing + Markdown support for frontend + block color display fix
2025-05-16 22:31:16 +02:00
martin legrand 713c01193f sec: no crossorigin allow 2025-05-16 14:05:36 +02:00
martin legrand 50a9cb8d27 fix : frontend color 2025-05-16 12:41:56 +02:00
martin legrand bd26d7233d refactor : comments + feat : selected lang in config influe browser config 2025-05-16 10:18:26 +02:00
martin legrand 45fbf5a88c fix comment 2025-05-16 09:24:46 +02:00
martin legrand 384d9a8c0b feat : markdown support on frontend 2025-05-16 09:19:45 +02:00
martin legrand 95d5aea1d5 feat : upgrade stealthness 2025-05-16 09:04:19 +02:00
martin legrand 38b1e17628 feat : better browser spoofing 2025-05-15 15:13:25 +02:00
martin legrand 637ca0f826 feat : attempt to bypass bot detection even more 2025-05-14 21:55:59 +02:00
martin legrand 201b3de15c feat : attempt to bypass bot detection 2025-05-14 21:43:13 +02:00
martin legrand 4739a1377c install: improve sh scripts 2025-05-14 18:50:40 +02:00
Martin 4bb7a21604 Merge pull request #181 from CoruNethron/main
Update README.md to match recent code changes
2025-05-12 22:59:19 +02:00
CoruNethron 725a3c3292 Update README.md to match recent code changes
`deepseek-api` was renamed to `deepseek`
`server` directory is now `llm_server`
2025-05-12 15:46:24 +10:00
Martin e72072090e Merge pull request #176 from Fosowl/dev
Fix connection issue with 0.0.0.0 in app.js + more wide browser view
2025-05-08 13:49:31 +02:00
martin legrand ef91502961 fix : connection issue with 0.0.0.0 in app.js + unzoom browser view 2025-05-08 13:38:07 +02:00
Martin 2048af854f Merge pull request #170 from Fosowl/dev
clarification in readme
2025-05-06 22:45:36 +02:00
martin legrand dd6ddaeca3 upd readme 2025-05-06 22:43:51 +02:00
martin legrand 47fec1914a comment out in progress mcp agent to avoid confusion 2025-05-06 22:39:15 +02:00
Martin 9ff69d1876 Merge pull request #168 from Fosowl/dev
update readme - enforce python 3.10
2025-05-06 20:24:44 +02:00
martin legrand ca2eea8089 update readme 2025-05-06 20:23:53 +02:00
Martin 3678c091ac Merge pull request #167 from rense/feature/remote-ollama
Allow connecting to a remote Ollama server
2025-05-06 19:49:18 +02:00
rense 94fb15359b allow connecting to remote Ollama server 2025-05-06 18:03:14 +02:00
Martin 3d1b3d02d9 Merge pull request #166 from Fosowl/dev
Update ja readme up to date
2025-05-06 11:02:13 +02:00
martin legrand c769e790bc upd: jp readme 2025-05-06 11:01:28 +02:00
martin legrand 23a51e6a05 upd: jp readme 2025-05-06 11:00:35 +02:00
martin legrand 94eada9d5d fix : return for json load 2025-05-05 19:11:56 +02:00
Martin 2cdbb49ecd Merge pull request #163 from Fosowl/dev
MCP Agent prototype (with no MCPs yet), Readme update, New function for memory system
2025-05-05 19:04:42 +02:00
MartinandCopilot dd033d4084 Update sources/memory.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-05-05 19:00:49 +02:00
martin legrand de2650c007 set limit_to_model_ctx to true 2025-05-05 18:36:37 +02:00
martin legrand deb79b81ca fix: typo 2025-05-05 18:32:06 +02:00
martin legrand 24dc1e1a2c fix: ensure logging is not on stdout/err 2025-05-05 18:29:42 +02:00
martin legrand af7619650a ja readme update 2025-05-05 18:17:58 +02:00
martin legrand df645f9a02 remove start ollama in windows scritp 2025-05-05 18:04:07 +02:00
martin legrand dc6eef8031 update readme 2025-05-05 18:01:02 +02:00
martin legrand 5c391dbb6e update japanese readme 2025-05-05 17:55:34 +02:00
martin legrand 101c103aeb fix : reset of blocks 2025-05-05 15:37:13 +02:00
martin legrand de315a43a1 upd readme 2025-05-05 14:23:10 +02:00
martin legrand 90f173ba52 upd readme 2025-05-05 14:22:18 +02:00
martin legrand e4591ea1b4 feat: improve compression/web agent memory management 2025-05-05 14:15:32 +02:00
martin legrand a7deffedec feat : improve memory system 2025-05-04 21:54:01 +02:00
martin legrand 5949540007 feat : tool description proprety 2025-05-04 19:29:31 +02:00
martin legrand 7afb79117b update agent 2025-05-04 18:49:04 +02:00
martin legrand 442bb4a340 Feat : MCP agent 2025-05-04 18:34:05 +02:00
Martin 99467be133 Merge pull request #162 from Fosowl/dev
upd readme examples
2025-05-04 12:02:27 +02:00
martin legrand 887060acdf upd readme 2025-05-04 12:01:44 +02:00
Martin 90609e960c Merge pull request #161 from Fosowl/dev
Update readme manual install instruction
2025-05-04 11:44:26 +02:00
martin legrand d893928221 update all readme 2025-05-04 11:43:32 +02:00
martin legrand 5bc086fd9d update fr readme 2025-05-04 11:24:40 +02:00
martin legrand aca176b9e7 update readme 2025-05-04 11:14:31 +02:00
Martin 9707dbcbf9 Merge pull request #159 from Fosowl/dev
Fixed tts not working with web interface
2025-05-03 19:23:16 +02:00
martin legrand 52e5af8116 fix : tts not working with web interface 2025-05-03 19:22:01 +02:00
Martin 42058244f2 Merge pull request #158 from Fosowl/dev
Chinese and Japanese Text-to-Speech support + readme update
2025-05-03 18:00:12 +02:00
martin legrand bddaa75e8c upd readme 2025-05-03 16:57:43 +02:00
martin legrand 7904439f35 feat : japanese tts support 2025-05-03 16:48:20 +02:00
martin legrand c873af3d00 fix : text to speech in chinese 2025-05-03 16:34:52 +02:00
Martin fa2852d3e7 Merge pull request #153 from Fosowl/dev
Update config.ini + fix requirement.txt + fix SSL issue with undetected chromedriver
2025-05-02 17:41:17 +02:00
martin legrand 1c4ebefae4 rm png file 2025-05-02 17:30:04 +02:00
martin legrand 96a6dd368a feat: fallback for ssl issues 2025-05-02 17:27:22 +02:00
martin legrand ed4f04b19c update requirement.txt 2025-05-02 17:26:26 +02:00
martin legrand f325865869 feat : update config 2025-05-02 16:56:52 +02:00
Martin a15dd998f3 Merge pull request #152 from Fosowl/dev
Improve chromedriver install error handling + improved fileFinder
2025-05-02 14:30:35 +02:00
martin legrand f17dc0550b feat: better fileFinder read 2025-05-02 14:23:00 +02:00
martin legrand ed76c8415b feat : fileFinder read pdf, browser better chromedriver install 2025-05-02 14:10:16 +02:00
Martin 9f2c105074 Merge pull request #151 from Fosowl/Fosowl-patch-1
Create FUNDING.yml
2025-05-02 11:38:48 +02:00
Martin ccef61b2b9 Create FUNDING.yml 2025-05-02 11:38:14 +02:00
martin legrand 3cf1cab68f cmv 2025-05-01 22:16:55 +02:00
martin legrand 0579fd3bb6 upd readme 2025-05-01 22:14:59 +02:00
martin legrand c6688355a7 set code safety on by default 2025-05-01 22:10:54 +02:00
martin legrand 68ed1834a9 fix : errors related to API based LLMs 2025-05-01 18:17:03 +02:00
Martin 487670d207 Merge pull request #147 from floriangab/docs/bump-conduct-and-contributing
Docs/bump conduct and contributing
2025-05-01 14:43:19 +02:00
florianG a109ac98ed docs: bump Python requirement from 3.8 to 3.10 and add Podman 2025-05-01 12:52:30 +02:00
florianG d928a95ed1 docs: update Code of Conduct from v2.0 to v2.1
- Bump version number in header/footer to 2.1
- Prepend reporting clause:
  “Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement: you need to send a private message to maintainers on Discord.”
- Adjust changelog entry accordingly
2025-05-01 12:46:07 +02:00
florianG ffa6873a86 fix: update Discord server invite link 2025-05-01 12:44:49 +02:00
Martin db2eb6fbac Merge pull request #144 from Fosowl/dev
Update readme
2025-04-29 21:39:21 +02:00
martin legrand 68d471bfc6 update readmes 2025-04-29 21:38:29 +02:00
martin legrand 03c71368f5 update readme 2025-04-29 21:19:09 +02:00
Martin 2fd83289fd Merge pull request #143 from Fosowl/dev
fix : start_services.sh not supporting newest docker compose (#137)
2025-04-29 12:02:50 +02:00
martin legrand 7dd60a8946 fix : start_services.sh not supporting newest docker compose (#137) 2025-04-29 12:01:56 +02:00
Martin ccaf1fae52 Merge pull request #141 from Fosowl/Fosowl-patch-3
Update README.md
2025-04-28 20:04:15 +02:00
Martin 34e9baccf3 Merge pull request #140 from Fosowl/Fosowl-patch-3
Update README.md
2025-04-28 10:42:36 +02:00
62 changed files with 3730 additions and 3602 deletions
+18
View File
@@ -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
+10 -1
View File
@@ -1,3 +1,12 @@
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'
DEEPSEEK_API_KEY='xxxxx'
DEEPSEEK_API_KEY='xxxxx'
OPENROUTER_API_KEY='xxxxx'
TOGETHER_API_KEY='xxxxx'
GOOGLE_API_KEY='xxxxx'
ANTHROPIC_API_KEY='xxxxx'
+4
View File
@@ -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]
+2
View File
@@ -9,6 +9,7 @@ test_agent.py
config.ini
.voices/
experimental/
chrome_bundle/
.logs/
.screenshots/*.png
.screenshots/*.jpg
@@ -18,6 +19,7 @@ agentic_seek_env/*
.env
*/.env
dsk/
chrome136/
### react ###
.DS_*
+83 -27
View File
@@ -1,46 +1,102 @@
FROM ubuntu:22.04
# Warning: doesn't work yet, backend is run on host machine for now
FROM --platform=linux/amd64 python:3.11-slim
ENV DEBIAN_FRONTEND=noninteractive
# Install essential packages and Chrome dependencies
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 \
g++ \
gfortran \
libportaudio2 \
portaudio19-dev \
ffmpeg \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
gnupg2 \
wget \
unzip \
python3 \
python3-pip \
libasound2 \
libatk-bridge2.0-0 \
libgtk-4-1 \
libnss3 \
xdg-utils \
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 apt-get update -qq -y && \
apt-get install -y \
gcc \
g++ \
gfortran \
libportaudio2 \
portaudio19-dev \
ffmpeg \
libavcodec-dev \
libavformat-dev \
libavutil-dev \
gnupg2 \
wget \
unzip \
python3 \
python3-pip \
libasound2 \
libatk-bridge2.0-0 \
libgtk-4-1 \
libnss3 \
xdg-utils \
wget && \
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
# Install dependencies
RUN pip3 install --upgrade pip setuptools wheel
COPY 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 api.py .
COPY sources/ ./sources/
COPY prompts/ ./prompts/
COPY crx/ crx/
COPY llm_router/ llm_router/
COPY .env .
COPY config.ini .
# Expose port
EXPOSE 8000
# Run the application
+150 -62
View File
@@ -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.*
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) [![GitHub stars](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers)
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460) [![GitHub stars](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers)
### 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.
> 🛠️ **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
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.
@@ -52,32 +56,77 @@ mv .env.example .env
```sh
python3 -m venv agentic_seek_env
source agentic_seek_env/bin/activate
source agentic_seek_env/bin/activate
# On Windows: agentic_seek_env\Scripts\activate
```
### 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
./install.sh
```
**Manually:**
For windows:
```sh
pip3 install -r requirements.txt
# or
python3 setup.py install
./install.bat
```
**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
**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**
@@ -91,26 +140,40 @@ See below for a list of local supported provider.
**Update the config.ini**
Change the config.ini file to set the provider_name to a supported provider and provider_model to `deepseek-r1:14b`
Change the config.ini file to set the provider_name to a supported provider and provider_model to a LLM supported by your provider. We 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
[MAIN]
is_local = True
is_local = True # Whenever you are running locally or with remote provider.
provider_name = ollama # or lm-studio, openai, etc..
provider_model = deepseek-r1:14b
provider_model = deepseek-r1:14b # choose a model that fit your hardware
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # name of your AI
recover_last_session = True # whenever to recover the previous session
save_session = True # whenever to remember the current session
speak = True # text to speech
listen = False # Speech to text, only for CLI
work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek.
jarvis_personality = False # Whenever to use a more "Jarvis" like personality (experimental)
languages = en zh # The list of languages, Text to speech will default to the first language on the list
[BROWSER]
headless_browser = True # Whenever to use headless browser, 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**
| Provider | Local? | Description |
|-----------|--------|-----------------------------------------------------------|
| ollama | Yes | Run LLMs locally with ease using ollama as a LLM provider |
| lm-studio | Yes | Run LLM locally with LM studio (set `provider_name` to `lm-studio`)|
| openai | Yes | Use openai compatible API |
| openai | Yes | Use openai compatible API (eg: llama.cpp server) |
Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
@@ -124,21 +187,34 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
## 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
[MAIN]
is_local = False
provider_name = openai
provider_model = gpt-4o
provider_server_address = 127.0.0.1:5000
provider_name = google
provider_model = gemini-2.0-flash
provider_server_address = 127.0.0.1:5000 # doesn't matter
```
Warning: Make sure there is not trailing space in the config.
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)
@@ -171,6 +247,8 @@ start ./start_services.cmd # Window
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.
Start the backend.
@@ -185,52 +263,30 @@ Go to `http://localhost:3000/` and you should see the web interface.
## 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
sudo ./start_services.sh
python3 cli.py
```
You can also use speech to text by setting `listen = True` in the config. Only for CLI mode.
You will be prompted with `>>> `
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`.
To exit, simply say/type `goodbye`.
Here are some example usage:
### Coding/Bash
> *Make a snake game in python!*
> *Make a snake game in python*
> *Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.*
> *Show me how to multiply matrice in C*
> *Write a Go program to calculate the factorial of a number, save it as factorial.go in your workspace*
> *Make a blackjack in golang*
> *Search my summer_pictures folder for all JPG files, rename them with todays 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.
@@ -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
@@ -265,7 +321,7 @@ Clone the repository and enter the `server/`folder.
```sh
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
cd agenticSeek/server/
cd agenticSeek/llm_server/
```
Install server specific requirements:
@@ -303,6 +359,8 @@ Next step: [Start services and run AgenticSeek](#Start-services-and-Run)
## 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:
```
@@ -393,6 +451,7 @@ The table below show the available providers:
| deepseek-api | No | Deepseek API (non-private) |
| huggingface| No | Hugging-Face API (non-private) |
| togetherAI | No | Use together AI API (non-private) |
| google | No | Use google gemini API (non-private) |
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.
## connection adapters Issues
```
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions'
```
Make sure you have `http://` in front of the provider IP address :
`provider_server_address = http://127.0.0.1:11434`
## SearxNG base URL must be provided
```
raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.")
ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.
```
Maybe you didn't move `.env.example` as `.env` ? You can also export SEARXNG_BASE_URL:
`export SEARXNG_BASE_URL="http://127.0.0.1:8080"`
## FAQ
**Q: What hardware do I need?**
@@ -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?**
This started as Side-Project we did out of interest about AI agents. Whats 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.
## Contribute
@@ -473,5 +553,13 @@ Were looking for developers to improve AgenticSeek! Check out open issues or
[![Star History Chart](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date)
## Maintainers:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758)
> [Fosowl](https://github.com/Fosowl) | Paris Time
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
> [steveh8758](https://github.com/steveh8758) | Taipei Time
## Special Thanks:
> [tcsenpai](https://github.com/tcsenpai) For dockerization of backend
+100 -42
View File
@@ -1,43 +1,32 @@
# AgenticSeek: Private, Local Manus Alternative.
<p align="center">
<img align="center" src="./media/whale_readme.jpg">
<p>
[English](./README.md) | 中文 | [日本語](./README_JP.md)
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
*一个 **100% 本地替代 Manus AI** 的方案,这款支持语音的 AI 助理能够自主浏览网页、编写代码和规划任务,同时将所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零云端依赖。*
**Manus AI 的本地替代品**,它是一个具有语音功能的大语言模型秘书,可以 Coding、访问你的电脑文件、浏览网页,并自动修正错误与反省,最重要的是不会向云端传送任何资料。采用 DeepSeek R1 等推理模型构建,完全在本地硬体上运行,进而保证资料的隐私。
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
### 为什么选择 AgenticSeek
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
* 🔒 完全本地化与隐私保护 - 所有功能都在您的设备上运行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、阅读、提取信息、填写网页表单 — 全程无需人工操作。
* 💻 自主编码助手 - 需要代码?它可以编写、调试并运行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
* 🧠 智能代理选择 - 您提问,它会自动选择最适合该任务的代理。就像拥有一个随时待命的专家团队。
* 📋 规划与执行复杂任务 - 从旅行规划到复杂项目 — 它能将大型任务分解为步骤,并利用多个 AI 代理完成工作。
* 🎙️ 语音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,让您能像科幻电影中一样与您的个人 AI 助手对话。
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
> *在大阪和东京深入搜寻人工智慧新创公司,至少找到 5 家,然后储存在 research_japan.txt 档案中*
> *你可以用 C 语言制作俄罗斯方块游戏吗?*
> *我想设定一个新的专案档案索引,命名为 mark2。*
## Features:
- **100% 本机运行**: 本机运行,不使用云端服务,所以资料绝不会散布出去,我的东西还是我的!不会被当作其他服务的训练资料。
- **文件的交互系统**: 使用 bash 去浏览本机资料和操作本机系统。
- **自主 Coding**: AgenticSeek 可以自己运行、Debug、编译 Python、C、Golang 和各种语言。
- **代理助理**: 不同的工作由不同的助理去处理问题。AgenticSeek 会自己寻找最适合的助理去做相对应的工作。
- **规划**: 对于复杂的任务,AgenticSeek 会交办给不同的助理进行规划和执行。
- **自主学习**: 自动在网路上寻找资料。
- **记忆功能**: 对于每次的对话进行统整、保存对话,并且在本地储存用户的使用习惯。
> 🛠️ **目前还在开发阶段** – 欢迎任何贡献者加入我们!
---
@@ -45,6 +34,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
确保已安装了 Chrome driverDocker 和 Python 3.10(或更新)。
我们强烈建议您使用 Python 3.10 进行设置,否则可能会发生依赖错误。
有关于 Chrome driver 的问题,请参见 **Chromedriver** 部分。
### 1️⃣ **复制储存库与设置环境变数**
@@ -71,13 +62,55 @@ source agentic_seek_env/bin/activate
./install.sh
```
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
```
pip3 install jieba cn2an
```
**手动安装:**
```sh
pip3 install -r requirements.txt
# or
python3 setup.py install
```
**注意:对于任何操作系统,请确保您安装的 ChromeDriver 与您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135,请参阅已知问题**
- *Linux*:
更新软件包列表:`sudo apt update`
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
安装与您的 Chrome 浏览器版本匹配的 ChromeDriver
`sudo apt install -y chromium-chromedriver`
安装 requirements`pip3 install -r requirements.txt`
- *Macos*:
更新 brew`brew update`
安装 chromedriver`brew install --cask chromedriver`
安装 portaudio`brew install portaudio`
升级 pip`python3 -m pip install --upgrade pip`
升级 wheel`pip3 install --upgrade setuptools wheel`
安装 requirements`pip3 install -r requirements.txt`
- *Windows*:
安装 pyreadline3`pip install pyreadline3`
手动安装 portaudio(例如,通过 vcpkg 或预编译的二进制文件),然后运行:`pip install pyaudio`
从以下网址手动下载并安装 chromedriverhttps://sites.google.com/chromium.org/driver/getting-started
将 chromedriver 放置在包含在您的 PATH 中的目录中。
安装 requirements`pip3 install -r requirements.txt`
## 在本地机器上运行 AgenticSeek
@@ -93,18 +126,37 @@ ollama serve
请参阅下方支持的本地提供者列表。
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并将 `provider_model` 设置为 `deepseek-r1:14b`
**更新 config.ini**
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型
修改 config.ini 文件以设置 provider_name 为支持的提供者,并将 provider_model 设置为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen**Deepseek*
请参见 README 末尾的 **FAQ** 部分了解所需硬件。
```sh
[MAIN]
is_local = True
provider_name = ollama # 或 lm-studio, openai 等
provider_model = deepseek-r1:14b
is_local = True # 无论是在本地运行还是使用远程提供者。
provider_name = ollama # 或 lm-studio, openai 等..
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # 您的 AI 助手的名称
recover_last_session = True # 是否恢复之前的会话
save_session = True # 是否记住当前会话
speak = True # 文本转语音
listen = False # 语音转文本,仅适用于命令行界面
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
languages = en zh # 语言列表,文本转语音将默认使用列表中的第一种语言
[BROWSER]
headless_browser = True # 是否使用无头浏览器,只有在使用网页界面时才推荐使用。
stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测
```
警告:使用 LM-studio 运行 LLM 时,请*不要*将 provider_name 设置为 `openai`。请将其设置为 `lm-studio`
注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234`
**本地提供者列表**
| 提供者 | 本地? | 描述 |
@@ -313,6 +365,8 @@ provider_server_address = x.x.x.x:3333
## 语音转文字
请注意,目前语音转文字功能仅支持英语。
预设状况下,语音转文字功能是停用的。若要启用它,请在 `config.ini` 档案中,将 `listen` 选项设为 `True`
```
@@ -469,7 +523,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
就其尺寸而言,Deepseek R1 在推理和使用方面表现出色。我们认为非常适合我们的需求,其他模型也很好用,但 Deepseek 是我们最后选定的模型。
**Q:我在执行时 `main.py` 时出现错误。我该怎么办?**
**Q:我在执行时 `cli.py` 时出现错误。我该怎么办?**
1. 确保 Ollama 正在运行(ollama serve
2.`config.ini``provider_name` 的框架选择正确。
@@ -508,6 +562,10 @@ DeepSeek R1 天生会说中文
[Contribution guide](./docs/CONTRIBUTING.md)
## 者:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758)
## 维护者:
> [Fosowl](https://github.com/Fosowl) | 巴黎时间
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
> [steveh8758](https://github.com/steveh8758) | 台北时间
+109 -60
View File
@@ -1,3 +1,5 @@
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
<p align="center">
<img align="center" src="./media/whale_readme.jpg">
<p>
@@ -5,40 +7,28 @@
--------------------------------------------------------------------------------
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
# AgenticSeek: 類似 Manus 但基於 Deepseek R1 Agents 的本地模型。
*一个 **100% 本地替代 Manus AI** 的方案,這款支持語音的 AI 助理能够自主瀏覽網頁、编寫代码和規劃任務,同时將所有用戶資料保留在您的裝置上。專門為本地推理模型量身打造,完全在您自己的硬體上執行,确保完全的隐私保护和零雲端依賴。*
**Manus AI 的本地替代品**,它是一個具有語音功能的大語言模型秘書,可以 Coding、訪問你的電腦文件、瀏覽網頁,並自動修正錯誤與反省,最重要的是不會向雲端傳送任何資料。採用 DeepSeek R1 等推理模型構建,完全在本地硬體上運行,進而保證資料的隱私。
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
### 为什么選擇 AgenticSeek
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
* 🔒 完全本地化與隐私保护 - 所有功能都在您的设备上運行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
* 🌐 智能網頁瀏覽 - AgenticSeek 能够自主瀏覽網頁 — 搜索、閱读、提取信息、填寫網页表單 — 全程无需人工操作。
* 💻 自主编码助手 - 需要代码?它可以编寫、调试并運行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
* 🧠 智能代理选择 - 您提问,它會自动选择最适合该任务的代理。就像拥有一个随时待命的專家团队。
* 📋 规划與执行复杂任务 - 从旅行规划到复杂项目 — 它能將大型任务分解为步骤,并利用多个 AI 代理完成工作。
* 🎙️ 語音功能 - 清晰、快速、未来感十足的語音與語音轉文本功能,讓您能像科幻电影中一样與您的个人 AI 助手对话。
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
> *在大阪和東京深入搜尋人工智慧新創公司,至少找到 5 家,然後儲存在 research_japan.txt 檔案中*
> *你可以用 C 語言製作俄羅斯方塊遊戲嗎?*
> *我想設定一個新的專案檔案索引,命名為 mark2。*
## Features:
- **100% 本機運行**: 本機運行,不使用雲端服務,所以資料絕不會散布出去,我的東西還是我的!不會被當作其他服務的訓練資料。
- **文件的交互系統**: 使用 bash 去瀏覽本機資料和操作本機系統。
- **自主 Coding**: AgenticSeek 可以自己運行、Debug、編譯 Python、C、Golang 和各種語言。
- **代理助理**: 不同的工作由不同的助理去處理問題。AgenticSeek 會自己尋找最適合的助理去做相對應的工作。
- **規劃**: 對於複雜的任務,AgenticSeek 會交辦給不同的助理進行規劃和執行。
- **自主學習**: 自動在網路上尋找資料。
- **記憶功能**: 對於每次的對話進行統整、保存對話,並且在本地儲存用戶的使用習慣。
> 🛠️ **目前還在開發階段** – 歡迎任何貢獻者加入我們!
---
@@ -46,6 +36,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
確保已安裝了 Chrome driverDocker 和 Python 3.10(或更新)。
我们强烈建议您使用 Python 3.10 進行設定,否则可能會发生依赖错误。
有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。
### 1️⃣ **複製儲存庫與設置環境變數**
@@ -72,46 +64,97 @@ source agentic_seek_env/bin/activate
./install.sh
```
** 若要將文字轉成語音(TTS)功能支持中文,你需要安装 jieba(中文分詞庫)和 cn2an(中文數字轉換庫):**
```
pip3 install jieba cn2an
```
**手動安裝:**
```sh
pip3 install -r requirements.txt
# or
python3 setup.py install
```
**注意:對於不同作業系統,請確保已經安装的 ChromeDriver 與您已安装的 Chrome 版本一致。可以執行 `google-chrome --version`。如果您的 Chrome 版本 > 135,請參考已知问题**
- *Linux*:
更新软件包列表:`sudo apt update`
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
安装與您的 Chrome 瀏覽器版本匹配的 ChromeDriver
`sudo apt install -y chromium-chromedriver`
安装 requirements`pip3 install -r requirements.txt`
- *Macos*:
更新 brew`brew update`
安装 chromedriver`brew install --cask chromedriver`
安装 portaudio`brew install portaudio`
升级 pip`python3 -m pip install --upgrade pip`
升级 wheel`pip3 install --upgrade setuptools wheel`
安装 requirements`pip3 install -r requirements.txt`
- *Windows*:
安装 pyreadline3`pip install pyreadline3`
手动安装 portaudio(例如,通过 vcpkg 或預編譯的二進制文件),然後運行:`pip install pyaudio`
从以下網址手动下载并安装 chromedriverhttps://sites.google.com/chromium.org/driver/getting-started
將 chromedriver 放置在包含在您的 PATH 中的目录中。
安装 requirements`pip3 install -r requirements.txt`
## 在本地機器上運行 AgenticSeek
**建議至少使用 Deepseek 14B 以上參數的模型,較小的模型難以使用助理功能並且很快就會忘記上下文之間的關係。**
**本地行助手**
**本地行助手**
启动你的本地提供者,例如使用 ollama:
啟動你的本地提供者,例如使用 ollama:
```sh
ollama serve
```
请参下方支持的本地提供者列表。
请参下方支持的本地提供者列表。
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并`provider_model` 设置为 `deepseek-r1:14b`
修改 config.ini 文件以設定 provider_name 为支持的提供者,并provider_model 設定为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen**Deepseek*
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型
请参见 README 末尾的 **FAQ** 部分了解所需硬件
```sh
[MAIN]
is_local = True
provider_name = ollama # 或 lm-studio, openai 等
provider_model = deepseek-r1:14b
is_local = True # 无论是在本地運行还是使用远程提供者。
provider_name = ollama # 或 lm-studio, openai 等..
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # 您的 AI 助手的名称
recover_last_session = True # 是否恢复之前的會话
save_session = True # 是否记住当前會话
speak = True # 文本轉語音
listen = False # 語音轉文本,僅适用于命令行界面
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
languages = en zh # 语言列表,文本轉語音將默认使用列表中的第一种语言
[BROWSER]
headless_browser = True # 是否使用无头瀏覽器,只有在使用網页界面时才推荐使用。
stealth_mode = True # 使用无法檢測的 selenium 来减少瀏覽器檢測
```
**本地提供者列表**
| 提供者 | 本地? | 描述 |
|-------------|--------|-------------------------------------------------------|
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地行 LLM |
| lm-studio | 是 | 使用 LM Studio 本地行 LLM `provider_name` 设置`lm-studio`|
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地行 LLM |
| lm-studio | 是 | 使用 LM Studio 本地行 LLM `provider_name` 設定`lm-studio`|
| openai | 否 | 使用兼容的 API |
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
@@ -141,14 +184,14 @@ provider_server_address = 127.0.0.1:5000
---
## Start services and Run
(启动服务并行)
(啟動服务并行)
如果需要,请激活你的 Python 环境。
```sh
source agentic_seek_env/bin/activate
```
启动所需的服务。这将启动 `docker-compose.yml` 中的所有服务,包括:
啟動所需的服务。这將啟動 `docker-compose.yml` 中的所有服务,包括:
- searxng
- redis(由 redis 提供支持)
- 前端
@@ -158,25 +201,25 @@ sudo ./start_services.sh # MacOS
start ./start_services.cmd # Windows
```
**选项 1:** 使用 CLI 界面行。
**選項 1:** 使用 CLI 界面行。
```sh
python3 cli.py
```
**选项 2:** 使用 Web 界面行。
**選項 2:** 使用 Web 界面行。
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
启动后端服务。
啟動後端服务。
```sh
python3 api.py
```
访问 `http://localhost:3000/`,你应该看到 Web 界面。
访问 `http://localhost:3000/`,你应该看到 Web 界面。
请注意,目前 Web 界面不支持消息流式传输
请注意,目前 Web 界面不支持消息流式傳輸
*如果你不知道如何開始,請參閱 **Usage** 部分*
@@ -185,9 +228,9 @@ python3 api.py
## Usage (使用方法)
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中设置语言选项
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中設定语言選項
languages = en zh
更多信息请参 Config 部分
更多信息请参 Config 部分
確定所有的核心檔案都啟用了,也就是執行過這條命令 `./start_services.sh` 然後你就可以使用 `python3 cli.py` 來啟動 AgenticSeek 了!
@@ -244,11 +287,11 @@ python3 cli.py
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
你該說:
- 进行网络搜索,找出哪些国家最适合自旅行
- 進行網路搜索,找出哪些国家最适合自旅行
而不是說:
- 你知道哪些国家适合自旅行?
- 你知道哪些国家适合自旅行?
---
@@ -314,6 +357,8 @@ provider_server_address = x.x.x.x:3333
## 語音轉文字
请注意,目前語音轉文字功能僅支援英语。
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`
```
@@ -470,7 +515,7 @@ https://googlechromelabs.github.io/chrome-for-testing/
就其尺寸而言,Deepseek R1 在推理和使用方面表現出色。我們認為非常適合我們的需求,其他模型也很好用,但 Deepseek 是我們最後選定的模型。
**Q:我在執行時 `main.py` 時出現錯誤。我該怎麼辦?**
**Q:我在執行時 `cli.py` 時出現錯誤。我該怎麼辦?**
1. 確保 Ollama 正在運行(ollama serve
2.`config.ini``provider_name` 的框架選擇正確。
@@ -493,13 +538,13 @@ https://googlechromelabs.github.io/chrome-for-testing/
**Q: 是否支持中文以外的语言?**
DeepSeek R1 天生说中文
DeepSeek R1 天生说中文
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
如果不设置中文?果可能是:你让它写代码,结果跳出来个"生代理"(虽然我们根本没有这个代理... 但系统一脸懵圈!)
如果不設定中文?果可能是:你讓它寫代码,结果跳出来个"生代理"(虽然我们根本没有这个代理... 但系统一脸懵圈!)
实际上下载一个小型翻译模型来协助任务分配
实际上下载一个小型翻译模型来协助任务分配
## 貢獻
@@ -509,6 +554,10 @@ DeepSeek R1 天生会说中文
[Contribution guide](./docs/CONTRIBUTING.md)
## 者:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758)
## 维护者:
> [Fosowl](https://github.com/Fosowl) | 巴黎時間 | (有时很忙)
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北時間 | (經常很忙)
> [steveh8758](https://github.com/steveh8758) | 台北時間 | (總是很忙)
+77 -18
View File
@@ -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.
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
> 🛠️ **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:
- **100% Local**: Fonctionne en local sur votre PC. Vos données restent les vôtres.
@@ -44,7 +43,9 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
## **Installation**
Assurez-vous davoir installé le pilote Chrome, Docker et Python 3.10 (ou une version plus récente).
Assurez-vous davoir installé le pilote Chrome, Docker et Python 3.10.
Nous vous conseillons fortement d'utiliser exactement Python 3.10 pour l'installation. Des erreurs de dépendances pourraient survenir autrement.
Pour les problèmes liés au pilote Chrome, consultez la section Chromedriver.
@@ -74,9 +75,44 @@ source agentic_seek_env/bin/activate
**Manuel:**
```sh
pip3 install -r requirements.txt
```
**Note : Pour tous les systèmes d'exploitation, assurez-vous que le ChromeDriver que vous installez correspond à la version de Chrome installée. Exécutez `google-chrome --version`. Consultez les problèmes connus si vous avez Chrome >135**
- *Linux*:
Mettre à jour la liste des paquets : `sudo apt update`
Installer les dépendances : `sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
Installer ChromeDriver correspondant à la version de votre navigateur Chrome :
`sudo apt install -y chromium-chromedriver`
Installer les prérequis : `pip3 install -r requirements.txt`
- *macOS*:
Mettre à jour brew : `brew update`
Installer chromedriver : `brew install --cask chromedriver`
Installer portaudio : `brew install portaudio`
Mettre à jour pip : `python3 -m pip install --upgrade pip`
Mettre à jour wheel : `pip3 install --upgrade setuptools wheel`
Installer les prérequis : `pip3 install -r requirements.txt`
- *Windows*:
Installer pyreadline3 : `pip install pyreadline3`
Installer portaudio manuellement (par exemple, via vcpkg ou des binaires précompilés) puis exécutez : `pip install pyaudio`
Télécharger et installer chromedriver manuellement depuis : https://sites.google.com/chromium.org/driver/getting-started
Placez chromedriver dans un répertoire inclus dans votre PATH.
Installer les prérequis : `pip3 install -r requirements.txt`
## Faire fonctionner sur votre machine
@@ -88,18 +124,35 @@ Lancer votre provider local, par exemple avec ollama:
ollama serve
```
Voyez la section **Provider** pour la liste de provideurs disponible.
**Configurer le config.ini**
Modifiez le fichier config.ini pour définir provider_name sur le nom d'un provideur et provider_model sur le LLM à utiliser.
Modifiez le fichier config.ini pour définir provider_name sur un fournisseur supporté et provider_model sur un LLM compatible avec votre fournisseur. Nous recommandons des modèles de raisonnement comme *Qwen* ou *Deepseek*.
Consultez la section **FAQ** à la fin du README pour connaître le matériel requis.
```sh
[MAIN]
is_local = True
provider_name = ollama # ou lm-studio, openai, etc...
provider_model = deepseek-r1:14b
is_local = True # Si vous exécutez localement ou avec un fournisseur distant.
provider_name = ollama # ou lm-studio, openai, etc..
provider_model = deepseek-r1:14b # choisissez un modèle adapté à votre matériel
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # nom de votre IA
recover_last_session = True # récupérer ou non la session précédente
save_session = True # mémoriser ou non la session actuelle
speak = True # synthèse vocale
listen = False # reconnaissance vocale, uniquement pour CLI
work_dir = /Users/mlg/Documents/workspace # L'espace de travail pour AgenticSeek.
jarvis_personality = False # Utiliser une personnalité plus "Jarvis", non recommandé avec des petits modèles
languages = en fr # Liste des langages, la synthèse vocale utilisera par défaut la première langue de la liste
[BROWSER]
headless_browser = True # Utiliser ou non le navigateur sans interface graphique, recommandé uniquement avec l'interface web.
stealth_mode = True # Utiliser selenium non détectable pour réduire la détection du navigateur
```
Remarque : Certains fournisseurs (ex : lm-studio) nécessitent `http://` devant l'adresse IP. Par exemple `http://127.0.0.1:1234`
**Liste des provideurs locaux**
| Fournisseur | Local ? | Description |
@@ -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.
## **Exécuter avec une API**
## **Exécuter avec une API externe**
AVERTISSEMENT : Assurez-vous quil ny a pas despace en fin de ligne dans la configuration.
Définissez is_local sur True si vous utilisez une API basée sur OpenAI localement.
Changez ladresse IP si votre API basée sur OpenAI fonctionne sur votre propre serveur.
```sh
[MAIN]
is_local = False
provider_name = openai
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.
## Config
@@ -434,6 +492,7 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
[Guide du contributeur](./docs/CONTRIBUTING.md)
## Auteurs/Mainteneurs:
## Mainteneurs:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758)
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
+319 -227
View File
@@ -1,61 +1,48 @@
# AgenticSeek: プライベートなローカルManus代替
<p align="center">
<img align="center" src="./media/whale_readme.jpg">
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek ロゴ">
<p>
--------------------------------------------------------------------------------
[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語
[English](./README.md) | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | 日本語
# AgenticSeek: Deepseek R1エージェントによって動作するManusのようなAI。
*Manus AIの**100%ローカルな代替**となるこの音声対応AIアシスタントは、自律的にウェブを閲覧し、コードを書き、タスクを計画しながら、すべてのデータをあなたのデバイスに保持します。ローカル推論モデルに合わせて調整されており、完全にあなたのハードウェア上で動作するため、完全なプライバシーとクラウドへの依存ゼロを保証します。*
[![AgenticSeekを訪問](https://img.shields.io/static/v1?label=ウェブサイト&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![ライセンス](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-参加する-7289DA?logo=discord&logoColor=white)](https://discord.gg/8hGDaME3TC) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=更新%20%40Fosowl)](https://x.com/Martin993886460) [![GitHubスター](https://img.shields.io/github/stars/Fosowl/agenticSeek?style=social)](https://github.com/Fosowl/agenticSeek/stargazers)
**Manus AIの完全なローカル代替品**、音声対応のAIアシスタントで、コードを書き、ファイルシステムを探索し、ウェブを閲覧し、ミスを修正し、データをクラウドに送信することなくすべてを行います。DeepSeek R1のような推論モデルを使用して構築されており、この自律エージェントは完全にハードウェア上で動作し、データのプライバシーを保護します。
### なぜAgenticSeekなのか?
[![Visit AgenticSeek](https://img.shields.io/static/v1?label=Website&message=AgenticSeek&color=blue&style=flat-square)](https://fosowl.github.io/agenticSeek.html) ![License](https://img.shields.io/badge/license-GPL--3.0-green) [![Discord](https://img.shields.io/badge/Discord-Join%20Us-7289DA?logo=discord&logoColor=white)](https://discord.gg/m37d7XxZ) [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/fosowl.svg?style=social&label=Update%20%40Fosowl)](https://x.com/Martin993886460)
* 🔒 完全ローカル&プライベート - すべてがあなたのマシン上で実行されます — クラウドなし、データ共有なし。あなたのファイル、会話、検索はプライベートに保たれます。
> 🛠️ **進行中の作業** – 貢献者を探しています!
* 🌐 スマートなウェブブラウジング - AgenticSeekは自分でインターネットを閲覧できます — 検索、読み取り、情報抽出、ウェブフォーム入力 — すべてハンズフリーで。
* 💻 自律型コーディングアシスタント - コードが必要ですか?Python、C、Go、Javaなどでプログラムを書き、デバッグし、実行できます — すべて監視なしで。
* 🧠 スマートエージェント選択 - あなたが尋ねると、タスクに最適なエージェントを自動的に見つけ出します。まるで専門家チームが助けてくれるようです。
* 📋 複雑なタスクの計画と実行 - 旅行計画から複雑なプロジェクトまで — 大きなタスクをステップに分割し、複数のAIエージェントを使って物事を成し遂げることができます。
https://github.com/user-attachments/assets/fe9e8006-0462-4793-8b31-25bd42c6d1eb
* 🎙️ 音声対応 - クリーンで高速、未来的な音声と音声認識により、まるでSF映画のパーソナルAIのように話しかけることができます。
### **デモ**
> *agenticSeekプロジェクトを検索し、必要なスキルを学び、その後CV_candidates.zipを開いて、プロジェクトに最も適した候補者を教えてください。*
https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316
*そしてもっと多くのことができます!*
免責事項:このデモは、表示されるすべてのファイル(例:CV_candidates.zip)を含め、完全に架空のものです。私たちは企業ではなく、候補者ではなくオープンソースの貢献者を求めています。
> *大阪と東京のAIスタートアップを深く調査し、少なくとも5つ見つけて、research_japan.txtファイルに保存してください*
> 🛠️ **作業中** – 貢献者を募集中です!
> *C言語でテトリスゲームを作れますか?*
## インストール
> *新しいプロジェクトファイルインデックスをmark2として設定したいです。*
Chrome Driver、Docker、Python 3.10がインストールされていることを確認してください。
セットアップにはPython 3.10を正確に使用することを強くお勧めします。そうでない場合、依存関係のエラーが発生する可能性があります。
## 特徴:
Chromeドライバーに関する問題については、**Chromedriver**セクションを参照してください。
- **100%ローカル**: クラウドなし、ハードウェア上で動作。データはあなたのものです。
- **ファイルシステムの操作**: bashを使用してファイルを簡単にナビゲートおよび操作します。
- **自律的なコーディング**: Python、C、Golangなどのコードを書き、デバッグし、実行できます。
- **エージェントルーティング**: タスクに最適なエージェントを自動的に選択します。
- **計画**: 複雑なタスクの場合、複数のエージェントを起動して計画および実行します。
- **自律的なウェブブラウジング**: 自律的なウェブナビゲーション。
- **メモリ**: 効率的なメモリとセッション管理。
---
## **インストール**
chrome driver、docker、およびpython3.10(またはそれ以降)がインストールされていることを確認してください。
chrome driverに関連する問題については、**Chromedriver**セクションを参照してください。
### 1️⃣ **リポジトリをクローンしてセットアップ**
### 1️⃣ **リポジトリのクローンとセットアップ**
```sh
git clone https://github.com/Fosowl/agenticSeek.git
@@ -63,99 +50,184 @@ cd agenticSeek
mv .env.example .env
```
### 2 **仮想環境作成**
### 2 **仮想環境作成**
```sh
python3 -m venv agentic_seek_env
source agentic_seek_env/bin/activate
source agentic_seek_env/bin/activate
# Windowsの場合: agentic_seek_env\Scripts\activate
```
### 3️⃣ **パッケージインストール**
### 3️⃣ **パッケージインストール**
**自動インストール:**
Python、Dockerとdocker compose、Google Chromeがインストールされていることを確認してください。
Python 3.10.0を推奨します。
**自動インストール(推奨):**
Linux/Macosの場合:
```sh
./install.sh
```
**手動で:**
** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:**
** 注意: 日本語のテキスト読み上げ(TTS)機能には多くの依存関係が必要で、問題が発生する可能性があります。mecabrcに関する問題が発生することがあります。現在のところ、この問題を修正する方法が見つかっていません。当面は日本語でのテキスト読み上げ機能を無効にすることをお勧めします。**
必要なライブラリをインストールする場合は以下のコマンドを実行してください:
```
pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi
pip install unidic-lite
python -m unidic download
```
Windowsの場合:
```sh
pip3 install -r requirements.txt
# または
python3 setup.py install
./install.bat
```
**手動:**
**注意:どのOSでも、インストールするChromeDriverがインストール済みのChromeバージョンと一致していることを確認してください。`google-chrome --version`を実行してください。Chrome >135の場合の既知の問題を参照してください。**
- *Linux*:
パッケージリストの更新:`sudo apt update`
依存関係のインストール:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
Chromeブラウザのバージョンに一致するChromeDriverのインストール:
`sudo apt install -y chromium-chromedriver`
要件のインストール:`pip3 install -r requirements.txt`
- *Macos*:
brewの更新:`brew update`
chromedriverのインストール:`brew install --cask chromedriver`
portaudioのインストール:`brew install portaudio`
pipのアップグレード:`python3 -m pip install --upgrade pip`
wheelのアップグレード:`pip3 install --upgrade setuptools wheel`
要件のインストール:`pip3 install -r requirements.txt`
- *Windows*:
pyreadline3のインストール:`pip install pyreadline3`
portaudioの手動インストール(例:vcpkgまたはビルド済みバイナリ経由)後、実行:`pip install pyaudio`
chromedriverの手動ダウンロードとインストール:https://sites.google.com/chromium.org/driver/getting-started
PATHに含まれるディレクトリにchromedriverを配置します。
要件のインストール:`pip3 install -r requirements.txt`
---
## ローカルマシンでLLMを実行するためのセットアップ
## マシン上でローカルにLLMを実行するためのセットアップ
**少なくともDeepseek 14B使用することをお勧めします。小さモデルは、特にウェブブラウジングのタスクで苦労する可能性があります。**
**少なくともDeepseek 14B使用を推奨します。より小さモデルは、特にウェブブラウジングのタスクで苦労ます。**
**ローカルプロバイダーをセットアップする**
たとえば、ollamaを使用してローカルプロバイダーを開始します:
**ローカルプロバイダーのセットアップ**
ローカルプロバイダーを開始します。例えばollamaの場合:
```sh
ollama serve
```
以下に、サポートされているローカルプロバイダーのリストを示します
サポートされているローカルプロバイダーのリストについては、以下を参照してください
**config.ini更新する**
**config.ini更新**
config.iniファイルを変更して、`provider_name`をサポートされているプロバイダーに設定し、`provider_model``deepseek-r1:14b`に設定します。
config.iniファイルを変更して、provider_nameをサポートされているプロバイダーにprovider_modelをプロバイダーがサポートするLLMに設定します。*Qwen*や*Deepseek*などの推論モデルを推奨します。
注意: `deepseek-r1:14b`は例です。ハードウェアが許可する場合は、より大きなモデルを使用してください。
必要なハードウェアについては、READMEの最後にある**FAQ**を参照してください。
```sh
[MAIN]
is_local = True
provider_name = ollama # または lm-studio、openai など
provider_model = deepseek-r1:14b
is_local = True # ローカルで実行するか、リモートプロバイダーで実行するか。
provider_name = ollama # またはlm-studio、openaiなど
provider_model = deepseek-r1:14b # ハードウェアに合ったモデルを選択してください
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # AIの名前
recover_last_session = True # 前のセッションを復元するかどうか
save_session = True # 現在のセッションを記憶するかどうか
speak = True # テキスト読み上げ
listen = False # 音声認識、CLIのみ
work_dir = /Users/mlg/Documents/workspace # AgenticSeekのワークスペース。
jarvis_personality = False # より「Jarvis」らしい性格を使用するかどうか(実験的)
languages = en zh # 言語のリスト、テキスト読み上げはリストの最初の言語にデフォルト設定されます
[BROWSER]
headless_browser = True # ヘッドレスブラウザを使用するかどうか、ウェブインターフェースを使用する場合のみ推奨。
stealth_mode = True # undetected seleniumを使用してブラウザ検出を減らす
```
警告:LM-studioを使用してLLMを実行する場合、provider_nameを`openai`に設定しないでください。`lm-studio`に設定してください。
注意:一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例:`http://127.0.0.1:1234`
**ローカルプロバイダーのリスト**
| プロバイダー | ローカル? | 説明 |
| プロバイダー | ローカル | 説明 |
|-----------|--------|-----------------------------------------------------------|
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
| lm-studio | はい | LM studioを使用してローカルでLLMを実行`provider_name``lm-studio`に設定)|
| openai | はい | OpenAI互換APIを使用 |
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
| lm-studio | はい | LM studioでLLMをローカル実行します`provider_name``lm-studio`に設定)|
| openai | はい | openai互換API(例:llama.cppサーバー)を使用します |
次のステップ: [サービス開始してAgenticSeek実行する](#Start-services-and-Run)
次のステップ[サービス開始AgenticSeek実行](#サービスの開始と実行)
*問題が発生している場合は、**既知の問題**セクションを参照してください*
*問題が発生し場合は、**既知の問題**セクションを参照してください*
*ハードウェアがDeepseekをローカルで実行できない場合は、**APIを使用した実行**セクションを参照してください*
*ハードウェアがローカルでdeepseekを実行できない場合は、**API実行**セクションを参照してください*
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
---
## APIを使用したセットアップ
## APIで実行するためのセットアップ
`config.ini`希望するプロバイダーを設定してください。
`config.ini`目的のプロバイダーを設定します。APIプロバイダーのリストについては、以下を参照してください。
```sh
[MAIN]
is_local = False
provider_name = openai
provider_model = gpt-4o
provider_server_address = 127.0.0.1:5000
provider_name = google
provider_model = gemini-2.0-flash
provider_server_address = 127.0.0.1:5000 # 関係ありません
```
警告:設定に末尾のスペースがないことを確認してください。
警告: `config.ini`に末尾のスペースがないことを確認してください。
APIキーをエクスポートします:`export <<PROVIDER>>_API_KEY="xxx"`
ローカルのOpenAIベースのAPIを使用する場合は、`is_local`をTrueに設定してください。
例:`export TOGETHER_API_KEY="xxxxx"`
OpenAIベースのAPIが独自のサーバーで実行されている場合は、IPアドレスを変更してください。
**APIプロバイダーのリスト**
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
| プロバイダー | ローカル? | 説明 |
|-----------|--------|-----------------------------------------------------------|
| openai | 場合による | ChatGPT APIを使用 |
| deepseek-api | いいえ | Deepseek API(非プライベート) |
| huggingface| いいえ | Hugging-Face API(非プライベート) |
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
| google | いいえ | google gemini APIを使用(非プライベート) |
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
*gpt-4oや他のclosedAIモデルの使用は推奨しません*。ウェブブラウジングやタスク計画のパフォーマンスが悪いです。
また、geminiではコーディング/bashが失敗する可能性があることに注意してください。deepseek r1用に最適化されたフォーマットのプロンプトを無視するようです。
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
*問題が発生した場合は、**既知の問題**セクションを参照してください*
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
@@ -163,30 +235,30 @@ OpenAIベースのAPIが独自のサーバーで実行されている場合は
## サービスの開始と実行
必要に応じてPython環境をアクティブにしてください
必要に応じてPython環境をアクティブ化します
```sh
source agentic_seek_env/bin/activate
```
必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます:
- searxng
- redis (searxngに必要)
- フロントエンド
必要なサービスを開始します。これにより、docker-compose.ymlからすべてのサービスが開始されます。これには以下が含まれます:
- searxng
- redissearxngに必要
- frontend
```sh
sudo ./start_services.sh # MacOS
start ./start_services.cmd # Windows
start ./start_services.cmd # Window
```
**オプション1:** CLIインターフェースで実行。
**オプション1** CLIインターフェースで実行します
```sh
python3 cli.py
```
**オプション2:** Webインターフェースで実行
CLIモードでは、config.iniで`headless_browser`をFalseに設定することをお勧めします
注意: 現在、CLIの使用を推奨しています。Webインターフェースは開発中です。
**オプション2** Webインターフェースで実行します。
バックエンドを開始します。
@@ -196,113 +268,89 @@ python3 api.py
`http://localhost:3000/`にアクセスすると、Webインターフェースが表示されます。
現在、Webインターフェースではメッセージのストリーミングがサポートされていないことに注意してください。
---
## 使用方法
`./start_services.sh`でサービスが起動していることを確認し、CLIモードの場合は`python3 cli.py`で、Webインターフェースの場合は`python3 api.py`を実行してから`localhost:3000`にアクセスしてAgenticSeekを実行します。
設定で`listen = True`を設定することで、音声認識を使用することもできます。CLIモードのみ。
終了するには、単に`goodbye`と発言/入力します。
以下に使用例をいくつか示します:
> *Pythonでスネークゲームを作って!*
> *フランスのレンヌでトップのカフェをウェブ検索し、3つのカフェのリストとその住所をrennes_cafes.txtに保存して。*
> *数値の階乗を計算するGoプログラムを書いて、それをfactorial.goとしてワークスペースに保存して。*
> *summer_picturesフォルダ内のすべてのJPGファイルを検索し、今日の日付で名前を変更し、名前変更されたファイルのリストをphotos_list.txtに保存して。*
> *2024年の人気のSF映画をオンラインで検索し、今夜観る映画を3つ選んで。リストをmovie_night.txtに保存して。*
> *2025年の最新AIニュース記事をウェブで検索し、3つ選択して、それらのタイトルと要約をスクレイピングするPythonスクリプトを書いて。スクリプトをnews_scraper.pyとして、要約を/home/projectsのai_news.txtに保存して。*
> *金曜日、無料の株価APIをウェブで検索し、supersuper7434567@gmail.comで登録し、そのAPIを使用してテスラの日々の価格を取得するPythonスクリプトを書いて、結果をstock_prices.csvに保存して。*
*フォーム入力機能はまだ実験的であり、失敗する可能性があることに注意してください。*
クエリを入力すると、AgenticSeekはタスクに最適なエージェントを割り当てます。
これは初期のプロトタイプであるため、エージェントルーティングシステムがクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
したがって、何をしたいのか、AIがどのように進むべきかについて非常に明確にする必要があります。たとえば、ウェブ検索を実行させたい場合は、次のように言わないでください:
`一人旅に適した良い国を知っていますか?`
代わりに、次のように尋ねてください:
`ウェブ検索をして、一人旅に最適な国を見つけてください`
---
## 使い方
## **独自のサーバーでLLMを実行するためのセットアップ**
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
サービスが`./start_services.sh`で起動していることを確認し、`python3 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を実行するオプションがあります。
強力なコンピューターまたは使用できるサーバーがあるが、ラップトップから使用したい場合は、カスタムLLMサーバーを使用してリモートサーバーでLLMを実行するオプションがあります。
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
```sh
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP
curl https://ipinfo.io/ip # 公開IP
curl https://ipinfo.io/ip # パブリックIP
```
注意: WindowsまたはmacOSの場合、IPアドレスを見つけるには、それぞれ`ipconfig`または`ifconfig`を使用してください
注意WindowsまたはmacOSの場合、それぞれipconfigまたはifconfigを使用してIPアドレスを見つけます
リポジトリをクローンし、`server/`フォルダに入ります。
リポジトリをクローンし、`server/`フォルダーに移動します。
```sh
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
cd agenticSeek/server/
```
サーバー固有の依存関係をインストールします:
サーバー固有の要件をインストールします
```sh
pip3 install -r requirements.txt
```
サーバースクリプトを実行します。
サーバー スクリプトを実行します。
```sh
python3 app.py --provider ollama --port 3333
```
`ollama``llamacpp`のどちらかをLLMサービスとして選択できます。
LLMサービスとして`ollama``llamacpp`のどちらかを選択できます。
次に、個人用コンピュータで以下を行います:
`config.ini`ファイルを変更し、`provider_name``server`に、`provider_model``deepseek-r1:xxb`に設定します。
次に、個人のコンピュータで:
`config.ini`ファイルを変更して、`provider_name``server`に、`provider_model``deepseek-r1:xxb`に設定します。
`provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。
```sh
@@ -313,44 +361,46 @@ provider_model = deepseek-r1:70b
provider_server_address = x.x.x.x:3333
```
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
---
## 音声認識
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
現在、音声認識は英語でのみ機能することに注意してください。
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
```
listen = True
```
有効にすると、音声認識機能はトリガーキーワード(エージェントの名前)を待ちます。その後、入力を処理します。エージェントの名前は*config.ini*ファイル`agent_name`値を更新することでカスタマイズできます:
有効にすると、音声認識機能は、入力を処理し始める前にトリガーキーワード(エージェントの名前)をリッスンします。*config.ini*ファイル`agent_name`値を更新することで、エージェントの名前をカスタマイズできます
```
agent_name = Friday
```
最適な認識のために、"John"や"Emma"のような一般的な英語の名前をエージェント名として使用することをお勧めします。
最適な認識のためには、エージェント名として「John」や「Emmaのような一般的な英語の名前を使用することをお勧めします。
トランスクリプトが表示され始めたら、エージェントの名前を大声で言って起動します(例:"Friday")。
トランスクリプトが表示され始めたら、エージェントの名前を声に出して起動します(例:Friday)。
クエリを明確に話します。
クエリをはっきりと話します。
リクエストを終了する際に確認フレーズを使用してシステムに進行を通知します。確認フレーズの例は次のようなものがあります:
システムに処理を進めるよう合図するために、確認フレーズでリクエストを終了します。確認フレーズの例は次のとおりです:
```
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
```
## 設定
設定例:
設定例
```
[MAIN]
is_local = True
provider_name = ollama
provider_model = deepseek-r1:1.5b
provider_model = deepseek-r1:32b
provider_server_address = 127.0.0.1:11434
agent_name = Friday
recover_last_session = False
@@ -359,7 +409,7 @@ speak = False
listen = False
work_dir = /Users/mlg/Documents/ai_folder
jarvis_personality = False
languages = en ja
languages = en zh
[BROWSER]
headless_browser = False
stealth_mode = False
@@ -367,112 +417,154 @@ stealth_mode = False
**説明**:
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。
- provider_name -> 使用するプロバイダー(`ollama``server``lm-studio``deepseek-api`のいずれか)。
- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。
- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。
- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます
- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。
- save_session -> セッションデータを保存する(True)か、しない(False)
- speak -> 音声出力を有効にする(True)か、しない(False)。
- listen -> 音声入力を有効にする(True)か、しない(False)
- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。
- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)かしない(False。これは単にプロンプトファイルを変更するだけです
- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。
- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)
- provider_name -> 使用するプロバイダー(`ollama``server``lm-studio``deepseek-api`のいずれか)
- provider_model -> 使用するモデル、例:deepseek-r1:32b
- provider_server_address -> サーバーアドレス、例:ローカルの場合は127.0.0.1:11434。非ローカルAPIの場合は何でも設定します
- agent_name -> エージェントの名前、例:Friday。TTSのトリガーワードとして使用されます
- recover_last_session -> 前回のセッションから再開する(True)かしない(False
- save_session -> セッションデータを保存する(True)かしない(False)か
- speak -> 音声出力を有効にする(True)かしない(False)か。
- listen -> 音声入力をリッスンする(True)かしない(False)か。
- work_dir -> AIがアクセスできるフォルダ。例:/Users/user/Documents/。
- jarvis_personality -> JARVISのような性格を使用する(True)かしない(False)か。これは単にプロンプトファイルを変更します。
- languages -> サポートされている言語のリスト。LLMルーターが正しく機能するために必要です。あまりにも多くの言語や類似した言語を入れすぎないようにしてください。
- headless_browser -> 表示ウィンドウなしでブラウザを実行する(True)かしない(False)か。
- stealth_mode -> ボット検出を困難にします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
- languages -> サポートされている言語のリスト。エージェントルーティングシステムに必要です。言語リストが長いほど、ダウンロードされるモデルが多くなります。
## プロバイダー
以下の表は利用可能なプロバイダーを示しています:
以下の表は利用可能なプロバイダーを示しています
| プロバイダー | ローカル? | 説明 |
| プロバイダー | ローカル | 説明 |
|-----------|--------|-----------------------------------------------------------|
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 |
| lm-studio | はい | LM studio`lm-studio`を使用してローカルでLLMを実行 |
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
| deepseek-api | いいえ | Deepseek API(非プライベート) |
| huggingface| いいえ | Hugging-Face API(非プライベート) |
| togetherAI | いいえ | together AI API(非プライベート)を使用
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行します |
| lm-studio | はい | LM studioでLLMをローカル実行します`lm-studio` |
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
| deepseek-api | いいえ | Deepseek API(非プライベート) |
| huggingface| いいえ | Hugging-Face API(非プライベート) |
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
| google | いいえ | google gemini APIを使用(非プライベート) |
プロバイダーを選択するには、config.iniを変更します:
プロバイダーを選択するには、config.iniを変更します:
```
is_local = False
provider_name = openai
provider_model = gpt-4o
is_local = True
provider_name = ollama
provider_model = deepseek-r1:32b
provider_server_address = 127.0.0.1:5000
```
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalse。
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalseである必要があります
`provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。
`provider_model`: エージェントが使用するモデルを設定します。
`provider_server_address`: サーバープロバイダーを使用しない場合は何でも設定できます
`provider_server_address`: サーバーアドレス。APIプロバイダーには使用されません
# 既知の問題
## Chromedriverの問題
**既知のエラー#1:** *chromedriverの不一致*
**既知のエラー #1:** *chromedriverの不一致*
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
Current browser version is 134.0.6998.89 with binary path`
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
最新バージョンをダウンロードするには、次のリンクにアクセスしてください:
最新バージョンをダウンロードするためにナビゲートする必要があります:
https://developer.chrome.com/docs/chromedriver/downloads
Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください:
Chromeバージョン115以降を使用している場合は、以下にアクセスしてください
https://googlechromelabs.github.io/chrome-for-testing/
お使いのOSに対応するchromedriverバージョンをダウンロードします。
そして、OSに一致するchromedriverバージョンをダウンロードします。
![alt text](./media/chromedriver_readme.png)
![代替テキスト](./media/chromedriver_readme.png)
このセクションが不完全な場合は、問題を報告してください。
このセクションが不完全な場合は、問題を提起してください。
## 接続アダプタの問題
```
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:11434/v1/chat/completions'
```
プロバイダーのIPアドレスの前に`http://`があることを確認してください:
`provider_server_address = http://127.0.0.1:11434`
## SearxNGのベースURLを指定する必要があります
```
raise ValueError("SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.")
ValueError: SearxNG base URL must be provided either as an argument or via the SEARXNG_BASE_URL environment variable.
```
`.env.example``.env`として移動しなかった可能性がありますか?SEARXNG_BASE_URLをエクスポートすることもできます:
`export SEARXNG_BASE_URL="http://127.0.0.1:8080"`
## FAQ
**Q: どのようなハードウェアが必要ですか?**
**Q: どのようなハードウェアが必要ですか?**
| モデルサイズ | GPU | コメント |
|-----------|--------|-----------------------------------------------------------|
| 7B | 8GB VRAM | ⚠️ 推奨されません。パフォーマンスがく、頻繁に幻覚を起こし、プランナーエージェント失敗する可能性が高いです。 |
| 14B | 12GB VRAM (例: RTX 3060) | ✅ 簡単なタスクには使用可能です。ウェブブラウジングや計画タスクには苦労する可能性があります。 |
| 32B | 24GB以上のVRAM (例: RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画にはまだ苦労する可能性があります。 |
| 70B+ | 48GB以上のVRAM (例: Mac Studio) | 💪 優れた性能。高度なユースケースに推奨されます。 |
| モデルサイズ | GPU | コメント |
|-----------|------------|--------------------------------------------------------------------------|
| 7B | 8GB VRAM | ⚠️ 推奨。パフォーマンスがく、幻覚が頻繁に発生し、プランナーエージェント失敗する可能性が高いです。 |
| 14B | 12GB VRAM(例:RTX 3060 | ✅ 簡単なタスクには使用可能。ウェブブラウジングや計画タスク苦労する可能性があります。 |
| 32B | 24GB以上のVRAM(例:RTX 4090 | 🚀 ほとんどのタスクで成功しますが、タスク計画まだ苦労する可能性があります。 |
| 70B+ | 48GB以上のVRAM(例:mac studio | 💪 素晴らしい。高度なユースケースに推奨されます。 |
**Q: なぜ他のモデルではなくDeepseek R1を選ぶのですか?**
**Q: なぜ他のモデルではなくDeepseek R1のですか?**
Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに合っていると考えており、他のモデルも正常に動作しますが、Deepseekが私たちの主な選択です。
**Q: `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を避けことです。
私たちはJarvisFriday(アイアンマン映画)からインスピレーションを得て「クール」にしようとしましたが、機能性に関してはManusから多くのインスピレーションを得ています。なぜなら、人々が最初に求めているのはローカルManusの代替だからです。
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIコストを回避します。
これは、AIエージェントへの関心から始たサイドプロジェクトです。特別なは、ローカルモデルを使用し、APIを避けたいということです。
私たちはJarvisFriday(アイアンマン映画)からインスピレーションを得て「クール」にしましたが、機能性についてはManusからより多くのインスピレーションを得ています。なぜなら、それが人々が最初に望むもの、つまりローカルManusの代替だからです。
Manusとは異なり、AgenticSeekは外部システムからの独立性を優先し、より多くの制御、プライバシーを提供し、APIコストを回避します。
## 貢献
## 貢献する
AgenticSeekを改善するための開発者をしています!オープンな問題やディスカッションを確認してください。
AgenticSeekを改善するための開発者を募集しています!オープンな問題やディスカッションを確認してください。
[![Star History Chart](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date)
[貢献ガイド](./docs/CONTRIBUTING.md)
## 著者:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758)
[![スター履歴チャート](https://api.star-history.com/svg?repos=Fosowl/agenticSeek&type=Date)](https://www.star-history.com/#Fosowl/agenticSeek&Date)
## メンテナー:
> [Fosowl](https://github.com/Fosowl) | パリ時間
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
> [steveh8758](https://github.com/steveh8758) | 台北時間 |(常に忙しい)
+28 -6
View File
@@ -22,6 +22,10 @@ from sources.utility import pretty_print
from sources.logger import Logger
from sources.schemas import QueryRequest, QueryResponse
from dotenv import load_dotenv
load_dotenv()
from celery import Celery
@@ -58,7 +62,7 @@ def initialize_system():
logger.info(f"Provider initialized: {provider.provider_name} ({provider.model})")
browser = Browser(
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode, lang=languages[0]),
anticaptcha_manual_install=stealth_mode
)
logger.info("Browser initialized")
@@ -128,6 +132,12 @@ async def is_active():
logger.info("Is active endpoint called")
return {"is_active": interaction.is_active}
@api.get("/stop")
async def stop():
logger.info("Stop endpoint called")
interaction.current_agent.request_stop()
return JSONResponse(status_code=200, content={"status": "stopped"})
@api.get("/latest_answer")
async def get_latest_answer():
global query_resp_history
@@ -138,6 +148,7 @@ async def get_latest_answer():
query_resp = {
"done": "false",
"answer": interaction.current_agent.last_answer,
"reasoning": interaction.current_agent.last_reasoning,
"agent_name": interaction.current_agent.agent_name if interaction.current_agent else "None",
"success": interaction.current_agent.success,
"blocks": {f'{i}': block.jsonify() for i, block in enumerate(interaction.get_last_blocks_result())} if interaction.current_agent else {},
@@ -145,27 +156,31 @@ async def get_latest_answer():
"uid": uid
}
interaction.current_agent.last_answer = ""
interaction.current_agent.last_reasoning = ""
query_resp_history.append(query_resp)
return JSONResponse(status_code=200, content=query_resp)
if query_resp_history:
return JSONResponse(status_code=200, content=query_resp_history[-1])
return JSONResponse(status_code=404, content={"error": "No answer available"})
async def think_wrapper(interaction, query, tts_enabled):
async def think_wrapper(interaction, query):
try:
interaction.tts_enabled = tts_enabled
interaction.last_query = query
logger.info("Agents request is being processed")
success = await interaction.think()
if not success:
interaction.last_answer = "Error: No answer from agent"
interaction.last_reasoning = "Error: No reasoning from agent"
interaction.last_success = False
else:
interaction.last_success = True
pretty_print(interaction.last_answer)
interaction.speak_answer()
return success
except Exception as e:
logger.error(f"Error in think_wrapper: {str(e)}")
interaction.last_answer = f"Error: {str(e)}"
interaction.last_answer = f""
interaction.last_reasoning = f"Error: {str(e)}"
interaction.last_success = False
raise e
@@ -176,6 +191,7 @@ async def process_query(request: QueryRequest):
query_resp = QueryResponse(
done="false",
answer="",
reasoning="",
agent_name="Unknown",
success="false",
blocks={},
@@ -188,11 +204,12 @@ async def process_query(request: QueryRequest):
try:
is_generating = True
success = await think_wrapper(interaction, request.query, request.tts_enabled)
success = await think_wrapper(interaction, request.query)
is_generating = False
if not success:
query_resp.answer = interaction.last_answer
query_resp.reasoning = interaction.last_reasoning
return JSONResponse(status_code=400, content=query_resp.jsonify())
if interaction.current_agent:
@@ -207,11 +224,11 @@ async def process_query(request: QueryRequest):
logger.info(f"Blocks: {blocks_json}")
query_resp.done = "true"
query_resp.answer = interaction.last_answer
query_resp.reasoning = interaction.last_reasoning
query_resp.agent_name = interaction.current_agent.agent_name
query_resp.success = str(interaction.last_success)
query_resp.blocks = blocks_json
# Store the raw dictionary representation
query_resp_dict = {
"done": query_resp.done,
"answer": query_resp.answer,
@@ -234,4 +251,9 @@ async def process_query(request: QueryRequest):
interaction.save_session()
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)
+7 -3
View File
@@ -7,7 +7,7 @@ import asyncio
from sources.llm_provider import Provider
from sources.interaction import Interaction
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent
from sources.browser import Browser, create_driver
from sources.utility import pretty_print
@@ -29,7 +29,7 @@ async def main():
is_local=config.getboolean('MAIN', 'is_local'))
browser = Browser(
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode, lang=languages[0]),
anticaptcha_manual_install=stealth_mode
)
@@ -48,7 +48,10 @@ async def main():
provider=provider, verbose=False, browser=browser),
PlannerAgent(name="Planner",
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
provider=provider, verbose=False, browser=browser)
provider=provider, verbose=False, browser=browser),
#McpAgent(name="MCP Agent",
# prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
# provider=provider, verbose=False), # NOTE under development
]
interaction = Interaction(agents,
@@ -62,6 +65,7 @@ async def main():
interaction.get_user()
if await interaction.think():
interaction.show_answer()
interaction.speak_answer()
except Exception as e:
if config.getboolean('MAIN', 'save_session'):
interaction.save_session()
+4 -4
View File
@@ -3,14 +3,14 @@ is_local = True
provider_name = ollama
provider_model = deepseek-r1:14b
provider_server_address = 127.0.0.1:11434
agent_name = Friday
agent_name = Name_of_your_AI
recover_last_session = False
save_session = False
speak = False
listen = False
work_dir = /Users/mlg/Documents/ai_folder
work_dir = /Users/mlg/Documents/workspace_for_agenticseek
jarvis_personality = False
languages = en
[BROWSER]
headless_browser = False
stealth_mode = True
headless_browser = True
stealth_mode = False
+34 -32
View File
@@ -3,6 +3,7 @@ version: '3'
services:
redis:
container_name: redis
profiles: ["core", "full"]
image: docker.io/valkey/valkey:8-alpine
command: valkey-server --save 30 1 --loglevel warning
restart: unless-stopped
@@ -24,6 +25,7 @@ services:
searxng:
container_name: searxng
profiles: ["core", "full"]
image: docker.io/searxng/searxng:latest
restart: unless-stopped
ports:
@@ -31,8 +33,8 @@ services:
volumes:
- ./searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=http://localhost:8080/
- SEARXNG_SECRET_KEY=$(openssl rand -hex 32)
- SEARXNG_BASE_URL=${SEARXNG_BASE_URL:-http://localhost:8080/}
- SEARXNG_SECRET_KEY=${SEARXNG_SECRET_KEY}
- UWSGI_WORKERS=4
- UWSGI_THREADS=4
cap_add:
@@ -51,6 +53,7 @@ services:
frontend:
container_name: frontend
profiles: ["core", "full"]
build:
context: ./frontend
dockerfile: Dockerfile.frontend
@@ -62,39 +65,38 @@ services:
environment:
- NODE_ENV=development
- CHOKIDAR_USEPOLLING=true
- BACKEND_URL=http://backend:8000
- REACT_APP_BACKEND_URL=http://0.0.0.0:${BACKEND_PORT:-8000}
networks:
- agentic-seek-net
# NOTE: backend service is not working yet due to issue with chromedriver on docker.
# Therefore backend is run on host machine.
# Open to pull requests to fix this.
#backend:
# container_name: backend
# build:
# context: ./
# dockerfile: Dockerfile.backend
# stdin_open: true
# tty: true
# shm_size: 8g
# ports:
# - "8000:8000"
# volumes:
# - ./:/app
# environment:
# - NODE_ENV=development
# - REDIS_URL=redis://redis:6379/0
# - SEARXNG_URL=http://searxng:8080
# - OLLAMA_URL=http://localhost:11434
# - LM_STUDIO_URL=http://localhost:1234
# extra_hosts:
# - "host.docker.internal:host-gateway"
# depends_on:
# - redis
# - searxng
# networks:
# - agentic-seek-net
backend:
container_name: backend
profiles: ["backend", "full"]
build:
context: .
dockerfile: Dockerfile.backend
ports:
- ${BACKEND_PORT:-7777}:${BACKEND_PORT:-7777}
- ${OLLAMA_PORT:-11434}:${OLLAMA_PORT:-11434}
- ${LM_STUDIO_PORT:-1234}:${LM_STUDIO_PORT:-1234}
- ${CUSTOM_ADDITIONAL_LLM_PORT:-8000}:${CUSTOM_ADDITIONAL_LLM_PORT:-8000}
volumes:
- ./:/app
- ${WORK_DIR:-.}:/opt/workspace
command: python3 api.py
environment:
- SEARXNG_URL=${SEARXNG_BASE_URL:-http://searxng:8080}
- REDIS_URL=${REDIS_BASE_URL:-redis://redis:6379/0}
- WORK_DIR=/opt/workspace
- OPENAI_API_KEY=${OPENAI_API_KEY}
- DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
- TOGETHER_API_KEY=${TOGETHER_API_KEY}
- GOOGLE_API_KEY=${GOOGLE_API_KEY}
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- HUGGINGFACE_API_KEY=${HUGGINGFACE_API_KEY}
- DSK_DEEPSEEK_API_KEY=${DSK_DEEPSEEK_API_KEY}
network_mode: "host"
volumes:
redis-data:
+30 -26
View File
@@ -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
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
@@ -22,17 +22,17 @@ community include:
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
@@ -52,15 +52,15 @@ decisions when appropriate.
This Code of Conduct applies within all community spaces, and also applies when
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
representative at an online or offline event.
## Enforcement
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 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
**Community Impact**: A violation through a single incident or series
of actions.
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
@@ -106,23 +106,27 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
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.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
version 2.1, available at
[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
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
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/translations.
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[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
+3 -3
View File
@@ -2,11 +2,11 @@
## Prerequisites
- Python 3.8 or higher.
- Docker or Orbstack.
- Python 3.10 or higher.
- Docker or Orbstack or Podman.
- Ollama with some deepseek-r1 variant installed or similar local reasoning model.
- 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
File diff suppressed because it is too large Load Diff
+1
View File
@@ -10,6 +10,7 @@
"axios": "^1.8.4",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-markdown": "^10.1.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
+86
View File
@@ -167,6 +167,88 @@ body {
margin-bottom: 8px;
}
/* Message header layout */
.message-header {
display: flex;
flex-direction: column;
align-items: flex-start;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.reasoning-toggle {
background: rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
color: #fff;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s ease;
display: flex;
align-items: center;
gap: 4px;
align-self: flex-start;
}
.reasoning-toggle:hover {
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.3);
}
.reasoning-toggle:active {
transform: translateY(1px);
}
/* Reasoning content container */
.reasoning-content {
margin-top: 12px;
padding: 12px;
background: rgba(0, 0, 0, 0.2);
border-left: 3px solid rgba(255, 255, 255, 0.3);
border-radius: 0 4px 4px 0;
font-size: 0.9em;
line-height: 1.4;
}
.reasoning-content h1,
.reasoning-content h2,
.reasoning-content h3,
.reasoning-content h4,
.reasoning-content h5,
.reasoning-content h6 {
font-size: 1em;
margin: 8px 0 4px 0;
color: rgba(255, 255, 255, 0.9);
}
.reasoning-content p {
margin: 6px 0;
color: rgba(255, 255, 255, 0.8);
}
/* Alternative light theme styles */
.message.user-message .reasoning-toggle {
background: rgba(0, 0, 0, 0.05);
border-color: rgba(0, 0, 0, 0.1);
color: #333;
}
.message.user-message .reasoning-toggle:hover {
background: rgba(0, 0, 0, 0.1);
border-color: rgba(0, 0, 0, 0.2);
}
.message.user-message .reasoning-content {
background: rgba(0, 0, 0, 0.03);
border-left-color: rgba(0, 0, 0, 0.2);
}
.message.user-message .reasoning-content p {
color: rgba(0, 0, 0, 0.7);
}
.placeholder {
text-align: center;
color: #64748b; /* lighter gray */
@@ -366,6 +448,10 @@ body {
color: #28a745; /* success */
}
.block-failure {
color: #d21b0b; /* success */
}
.block pre {
background-color: #1a202c; /* Darker than darkCard */
padding: 12px;
+65 -14
View File
@@ -1,8 +1,11 @@
import React, { useState, useEffect, useRef } from 'react';
import ReactMarkdown from 'react-markdown';
import axios from 'axios';
import './App.css';
import { colors } from './colors';
const BACKEND_URL = process.env.BACKEND_PORT || 'http://0.0.0.0:8000';
function App() {
const [query, setQuery] = useState('');
const [messages, setMessages] = useState([]);
@@ -12,6 +15,7 @@ function App() {
const [responseData, setResponseData] = useState(null);
const [isOnline, setIsOnline] = useState(false);
const [status, setStatus] = useState('Agents ready');
const [expandedReasoning, setExpandedReasoning] = useState(new Set());
const messagesEndRef = useRef(null);
useEffect(() => {
@@ -25,7 +29,7 @@ function App() {
const checkHealth = async () => {
try {
await axios.get('http://0.0.0.0:8000/health');
await axios.get(`${BACKEND_URL}/health`);
setIsOnline(true);
console.log('System is online');
} catch {
@@ -37,7 +41,7 @@ function App() {
const fetchScreenshot = async () => {
try {
const timestamp = new Date().getTime();
const res = await axios.get(`http://0.0.0.0:8000/screenshots/updated_screen.png?timestamp=${timestamp}`, {
const res = await axios.get(`${BACKEND_URL}/screenshots/updated_screen.png?timestamp=${timestamp}`, {
responseType: 'blob'
});
console.log('Screenshot fetched successfully');
@@ -74,9 +78,21 @@ function App() {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
};
const toggleReasoning = (messageIndex) => {
setExpandedReasoning(prev => {
const newSet = new Set(prev);
if (newSet.has(messageIndex)) {
newSet.delete(messageIndex);
} else {
newSet.add(messageIndex);
}
return newSet;
});
};
const fetchLatestAnswer = async () => {
try {
const res = await axios.get('http://0.0.0.0:8000/latest_answer');
const res = await axios.get(`${BACKEND_URL}/latest_answer`);
const data = res.data;
updateData(data);
@@ -93,6 +109,7 @@ function App() {
{
type: 'agent',
content: data.answer,
reasoning: data.reasoning,
agentName: data.agent_name,
status: data.status,
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) => {
e.preventDefault();
checkHealth();
@@ -134,7 +164,7 @@ function App() {
try {
console.log('Sending query:', query);
setQuery('waiting for response...');
const res = await axios.post('http://0.0.0.0:8000/query', {
const res = await axios.post(`${BACKEND_URL}/query`, {
query,
tts_enabled: false
});
@@ -171,8 +201,6 @@ function App() {
</header>
<main className="main">
<div className="app-sections">
<div className="chat-section">
<h2>Chat Interface</h2>
<div className="messages">
@@ -190,10 +218,28 @@ function App() {
: 'error-message'
}`}
>
{msg.type === 'agent' && (
<span className="agent-name">{msg.agentName}</span>
)}
<p>{msg.content}</p>
<div className="message-header">
{msg.type === 'agent' && (
<span className="agent-name">{msg.agentName}</span>
)}
{msg.type === 'agent' && msg.reasoning && expandedReasoning.has(index) && (
<div className="reasoning-content">
<ReactMarkdown>{msg.reasoning}</ReactMarkdown>
</div>
)}
{msg.type === 'agent' && (
<button
className="reasoning-toggle"
onClick={() => toggleReasoning(index)}
title={expandedReasoning.has(index) ? "Hide reasoning" : "Show reasoning"}
>
{expandedReasoning.has(index) ? '▼' : '▶'} Reasoning
</button>
)}
</div>
<div className="message-content">
<ReactMarkdown>{msg.content}</ReactMarkdown>
</div>
</div>
))
)}
@@ -212,6 +258,9 @@ function App() {
<button type="submit" disabled={isLoading}>
Send
</button>
<button onClick={handleStop}>
Stop
</button>
</form>
</div>
@@ -241,9 +290,11 @@ function App() {
<p className="block-tool">Tool: {block.tool_type}</p>
<pre>{block.block}</pre>
<p className="block-feedback">Feedback: {block.feedback}</p>
<p className="block-success">
Success: {block.success ? 'Yes' : 'No'}
</p>
{block.success ? (
<p className="block-success">Success</p>
) : (
<p className="block-failure">Failure</p>
)}
</div>
))
) : (
@@ -274,4 +325,4 @@ function App() {
);
}
export default App;
export default App;
+67
View File
@@ -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.
+62
View File
@@ -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
View File
@@ -1,3 +1,5 @@
kokoro==0.9.4
certifi==2025.4.26
fastapi>=0.115.12
flask>=3.1.0
celery>=5.5.1
@@ -15,13 +17,12 @@ playsound>=1.3.0
soundfile>=0.13.1
transformers>=4.46.3
torch>=2.4.1
python-dotenv>=1.0.0
ollama>=0.4.7
scipy>=1.9.3
kokoro>=0.7.12
soundfile>=0.13.1
protobuf>=3.20.3
termcolor>=2.4.0
pypdf>=5.4.0
ipython>=8.13.0
pyaudio>=0.2.14
librosa>=0.10.2.post1
@@ -39,11 +40,9 @@ fake_useragent>=2.1.0
selenium_stealth>=1.0.6
undetected-chromedriver>=3.5.5
sentencepiece>=0.2.0
together>=1.5.0
tqdm>4
openai
sniffio
tqdm>4
# if use chinese
ordered_set
pypinyin
cn2an
jieba
+21 -9
View File
@@ -3,10 +3,22 @@
echo "Starting installation for Linux..."
set -e
if ! command -v python3.10 &> /dev/null; then
echo "Error: Python 3.10 is not installed. Please install Python 3.10 and try again."
echo "You can install it using: sudo apt-get install python3.10 python3.10-dev python3.10-venv"
exit 1
fi
# Check if pip3.10 is available
if ! python3.10 -m pip --version &> /dev/null; then
echo "Error: pip for Python 3.10 is not installed. Installing python3.10-pip..."
sudo apt-get install -y python3.10-pip || { echo "Failed to install python3.10-pip"; exit 1; }
fi
# Update package list
sudo apt-get update || { echo "Failed to update package list"; exit 1; }
# make sure essential tool are installed
# Install essential tools
sudo apt-get install -y \
python3-dev \
python3-pip \
@@ -21,15 +33,15 @@ sudo apt-get install -y \
libnss3 \
libxss1 || { echo "Failed to install packages"; exit 1; }
# upgrade pip
pip install --upgrade pip
# install wheel
pip install --upgrade pip setuptools wheel
# Upgrade pip for Python 3.10
python3.10 -m pip install --upgrade pip || { echo "Failed to upgrade pip"; exit 1; }
# Install and upgrade setuptools and wheel
python3.10 -m pip install --upgrade setuptools wheel || { echo "Failed to install setuptools and wheel"; exit 1; }
# Install Selenium for chromedriver
python3.10 -m pip install selenium || { echo "Failed to install selenium"; exit 1; }
# Install Python dependencies from requirements.txt
python3.10 -m pip install -r requirements.txt --no-cache-dir || { echo "Failed to install requirements.txt"; exit 1; }
# install docker compose
sudo apt install -y docker-compose
# Install Selenium for chromedriver
pip3 install selenium
# Install Python dependencies from requirements.txt
pip3 install -r requirements.txt --no-cache-dir
echo "Installation complete for Linux!"
+20 -7
View File
@@ -4,6 +4,18 @@ echo "Starting installation for macOS..."
set -e
if ! command -v python3.10 &> /dev/null; then
echo "Error: Python 3.10 is not installed. Please install Python 3.10 and try again."
echo "You can install it using: sudo apt-get install python3.10 python3.10-dev python3.10-venv"
exit 1
fi
# Check if pip3.10 is available
if ! python3.10 -m pip --version &> /dev/null; then
echo "Error: pip for Python 3.10 is not installed. Installing python3.10-pip..."
sudo apt-get install -y python3.10-pip || { echo "Failed to install python3.10-pip"; exit 1; }
fi
# Check if homebrew is installed
if ! command -v brew &> /dev/null; then
echo "Homebrew not found. Installing Homebrew..."
@@ -18,13 +30,14 @@ brew install wget
brew install --cask chromedriver
# Install portaudio for pyAudio using Homebrew
brew install portaudio
# update pip
python3 -m pip install --upgrade pip
# upgrade setuptools and wheel
pip3 install --upgrade setuptools wheel
# Install Selenium
pip3 install selenium
# Upgrade pip for Python 3.10
python3.10 -m pip install --upgrade pip || { echo "Failed to upgrade pip"; exit 1; }
# Install and upgrade setuptools and wheel
python3.10 -m pip install --upgrade setuptools wheel || { echo "Failed to install setuptools and wheel"; exit 1; }
# Install Selenium for chromedriver
python3.10 -m pip install selenium || { echo "Failed to install selenium"; exit 1; }
# Install Python dependencies from requirements.txt
pip3 install -r requirements.txt --no-cache-dir
python3.10 -m pip install -r requirements.txt --no-cache-dir || { echo "Failed to install requirements.txt"; exit 1; }
echo "Installation complete for macOS!"
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -5,5 +5,6 @@ from .casual_agent import CasualAgent
from .file_agent import FileAgent
from .planner_agent import PlannerAgent
from .browser_agent import BrowserAgent
from .mcp_agent import McpAgent
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent"]
__all__ = ["Agent", "CoderAgent", "CasualAgent", "FileAgent", "PlannerAgent", "BrowserAgent", "McpAgent"]
+36 -5
View File
@@ -39,14 +39,14 @@ class Agent():
self.type = None
self.current_directory = os.getcwd()
self.llm = provider
self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False)
self.memory = None
self.tools = {}
self.blocks_result = []
self.success = True
self.last_answer = ""
self.last_reasoning = ""
self.status_message = "Haven't started yet"
self.stop = False
self.verbose = verbose
self.executor = ThreadPoolExecutor(max_workers=1)
@@ -66,6 +66,10 @@ class Agent():
def get_last_answer(self) -> str:
return self.last_answer
@property
def get_last_reasoning(self) -> str:
return self.last_reasoning
@property
def get_blocks(self) -> list:
return self.blocks_result
@@ -90,6 +94,21 @@ class Agent():
raise TypeError("Tool must be a callable object (a method)")
self.tools[name] = tool
def get_tools_name(self) -> list:
"""
Get the list of tools names.
"""
return list(self.tools.keys())
def get_tools_description(self) -> str:
"""
Get the list of tools names and their description.
"""
description = ""
for name in self.get_tools_name():
description += f"{name}: {self.tools[name].description}\n"
return description
def load_prompt(self, file_path: str) -> str:
try:
with open(file_path, 'r', encoding="utf-8") as f:
@@ -101,6 +120,13 @@ class Agent():
except Exception as e:
raise e
def request_stop(self) -> None:
"""
Request the agent to stop.
"""
self.stop = True
self.status_message = "Stopped"
@abstractmethod
def process(self, prompt, speech_module) -> str:
"""
@@ -114,8 +140,10 @@ class Agent():
Remove the reasoning block of reasoning model like deepseek.
"""
end_tag = "</think>"
end_idx = text.rfind(end_tag)+8
return text[end_idx:]
end_idx = text.rfind(end_tag)
if end_idx == -1:
return text
return text[end_idx+8:]
def extract_reasoning_text(self, text: str) -> None:
"""
@@ -123,6 +151,8 @@ class Agent():
"""
start_tag = "<think>"
end_tag = "</think>"
if text is None:
return None
start_idx = text.find(start_tag)
end_idx = text.rfind(end_tag)+8
return text[start_idx:end_idx]
@@ -238,6 +268,7 @@ class Agent():
blocks, save_path = tool.load_exec_block(answer)
if blocks != None:
pretty_print(f"Executing {len(blocks)} {name} blocks...", color="status")
for block in blocks:
self.show_block(block)
output = tool.execute([block])
+36 -12
View File
@@ -10,6 +10,7 @@ from sources.agents.agent import Agent
from sources.tools.searxSearch import searxSearch
from sources.browser import Browser
from sources.logger import Logger
from sources.memory import Memory
class Action(Enum):
REQUEST_EXIT = "REQUEST_EXIT"
@@ -37,6 +38,10 @@ class BrowserAgent(Agent):
self.notes = []
self.date = self.get_today_date()
self.logger = Logger("browser_agent.log")
self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False,
model_provider=provider.get_model_name() if provider else None)
def get_today_date(self) -> str:
"""Get the date"""
@@ -72,14 +77,14 @@ class BrowserAgent(Agent):
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])
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)
self.logger.info(f"Search results: {search_choice}")
return f"""
Based on the search result:
{search_choice}
Your goal is to find accurate and complete information to satisfy the users 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>"
Do not explain your choice.
"""
@@ -176,6 +181,7 @@ class BrowserAgent(Agent):
animate_thinking("Thinking...", color="status")
self.memory.push('user', prompt)
answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
if show_reasoning:
pretty_print(reasoning, color="failure")
pretty_print(answer, color="output")
@@ -229,15 +235,27 @@ class BrowserAgent(Agent):
return links
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:
if lk == self.current_page:
self.logger.info(f"Already visited {lk}. Skipping.")
if lk == self.current_page or lk in self.search_history:
self.logger.info(f"Skipping already visited or current link: {lk}")
continue
self.logger.info(f"Selected link: {lk}")
return lk
self.logger.warning("No link selected.")
self.logger.warning("No suitable link selected.")
return None
def get_page_text(self, limit_to_model_ctx = False) -> str:
"""Get the text content of the current page."""
page_text = self.browser.get_text()
if limit_to_model_ctx:
#page_text = self.memory.compress_text_to_max_ctx(page_text)
page_text = self.memory.trim_text_to_max_ctx(page_text)
return page_text
def conclude_prompt(self, user_query: str) -> str:
annotated_notes = [f"{i+1}: {note.lower()}" for i, note in enumerate(self.notes)]
search_note = '\n'.join(annotated_notes)
@@ -250,6 +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.
Do not give advices or try to answer the human. Just structure the AI finding in a structured and clear way.
You should answer in the same language as the user.
"""
def search_prompt(self, user_prompt: str) -> str:
@@ -335,11 +354,13 @@ class BrowserAgent(Agent):
self.show_search_results(search_result)
prompt = self.make_newsearch_prompt(user_prompt, search_result)
unvisited = [None]
while not complete and len(unvisited) > 0:
while not complete and len(unvisited) > 0 and not self.stop:
self.memory.clear()
unvisited = self.select_unvisited(search_result)
answer, reasoning = await self.llm_decide(prompt, show_reasoning = False)
if self.stop:
pretty_print(f"Requested stop.", color="failure")
break
if self.last_answer == answer:
prompt = self.stuck_prompt(user_prompt, unvisited)
continue
@@ -351,13 +372,13 @@ class BrowserAgent(Agent):
self.status_message = "Filling web form..."
pretty_print(f"Filling inputs form...", color="status")
fill_success = self.browser.fill_form(extracted_form)
page_text = self.browser.get_text()
page_text = self.get_page_text(limit_to_model_ctx=True)
answer = self.handle_update_prompt(user_prompt, page_text, fill_success)
answer, reasoning = await self.llm_decide(prompt)
if Action.FORM_FILLED.value in answer:
pretty_print(f"Filled form. Handling page update.", color="status")
page_text = self.browser.get_text()
page_text = self.get_page_text(limit_to_model_ctx=True)
self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text)
continue
@@ -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:
pretty_print(f"Going back to results. Still {len(unvisited)}", color="status")
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.current_page = link
continue
@@ -393,7 +417,7 @@ class BrowserAgent(Agent):
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
continue
self.current_page = link
page_text = self.browser.get_text()
page_text = self.get_page_text(limit_to_model_ctx=True)
self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text)
self.status_message = "Navigating..."
@@ -410,4 +434,4 @@ class BrowserAgent(Agent):
return answer, reasoning
if __name__ == "__main__":
pass
pass
+5
View File
@@ -6,6 +6,7 @@ from sources.tools.searxSearch import searxSearch
from sources.tools.flightSearch import FlightSearch
from sources.tools.fileFinder import FileFinder
from sources.tools.BashInterpreter import BashInterpreter
from sources.memory import Memory
class CasualAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False):
@@ -17,6 +18,10 @@ class CasualAgent(Agent):
} # No tools for the casual agent
self.role = "talk"
self.type = "casual_agent"
self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False,
model_provider=provider.get_model_name())
async def process(self, prompt, speech_module) -> str:
self.memory.push('user', prompt)
+11 -4
View File
@@ -10,6 +10,7 @@ from sources.tools.BashInterpreter import BashInterpreter
from sources.tools.JavaInterpreter import JavaInterpreter
from sources.tools.fileFinder import FileFinder
from sources.logger import Logger
from sources.memory import Memory
class CoderAgent(Agent):
"""
@@ -29,6 +30,10 @@ class CoderAgent(Agent):
self.role = "code"
self.type = "code_agent"
self.logger = Logger("code_agent.log")
self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False,
model_provider=provider.get_model_name())
def add_sys_info_prompt(self, prompt):
"""Add system information to the prompt."""
@@ -41,15 +46,17 @@ class CoderAgent(Agent):
async def process(self, prompt, speech_module) -> str:
answer = ""
attempt = 0
max_attempts = 4
max_attempts = 5
prompt = self.add_sys_info_prompt(prompt)
self.memory.push('user', prompt)
clarify_trigger = "REQUEST_CLARIFICATION"
while attempt < max_attempts:
while attempt < max_attempts and not self.stop:
print("Stopped?", self.stop)
animate_thinking("Thinking...", color="status")
await self.wait_message(speech_module)
answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
if clarify_trigger in answer:
self.last_answer = answer
await asyncio.sleep(0)
@@ -62,14 +69,14 @@ class CoderAgent(Agent):
animate_thinking("Executing code...", color="status")
self.status_message = "Executing code..."
self.logger.info(f"Attempt {attempt + 1}:\n{answer}")
exec_success, _ = self.execute_modules(answer)
exec_success, feedback = self.execute_modules(answer)
self.logger.info(f"Execution result: {exec_success}")
answer = self.remove_blocks(answer)
self.last_answer = answer
await asyncio.sleep(0)
if exec_success and self.get_last_tool_type() != "bash":
break
pretty_print("Execution failure", color="failure")
pretty_print(f"Execution failure:\n{feedback}", color="failure")
pretty_print("Correcting code...", color="status")
self.status_message = "Correcting code..."
attempt += 1
+7 -1
View File
@@ -4,6 +4,7 @@ from sources.utility import pretty_print, animate_thinking
from sources.agents.agent import Agent
from sources.tools.fileFinder import FileFinder
from sources.tools.BashInterpreter import BashInterpreter
from sources.memory import Memory
class FileAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False):
@@ -18,15 +19,20 @@ class FileAgent(Agent):
self.work_dir = self.tools["file_finder"].get_work_dir()
self.role = "files"
self.type = "file_agent"
self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False,
model_provider=provider.get_model_name())
async def process(self, prompt, speech_module) -> str:
exec_success = False
prompt += f"\nYou must work in directory: {self.work_dir}"
self.memory.push('user', prompt)
while exec_success is False:
while exec_success is False and not self.stop:
await self.wait_message(speech_module)
animate_thinking("Thinking...", color="status")
answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
exec_success, _ = self.execute_modules(answer)
answer = self.remove_blocks(answer)
self.last_answer = answer
+73
View File
@@ -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
+26 -9
View File
@@ -9,6 +9,7 @@ from sources.agents.casual_agent import CasualAgent
from sources.text_to_speech import Speech
from sources.tools.tools import Tools
from sources.logger import Logger
from sources.memory import Memory
class PlannerAgent(Agent):
def __init__(self, name, prompt_path, provider, verbose=False, browser=None):
@@ -29,6 +30,10 @@ class PlannerAgent(Agent):
}
self.role = "planification"
self.type = "planner_agent"
self.memory = Memory(self.load_prompt(prompt_path),
recover_last_session=False, # session recovery in handled by the interaction class
memory_compression=False,
model_provider=provider.get_model_name())
self.logger = Logger("planner_agent.log")
def get_task_names(self, text: str) -> List[str]:
@@ -78,11 +83,15 @@ class PlannerAgent(Agent):
self.logger.warning(f"Agent {task['agent']} does not exist.")
pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
return []
agent = {
'agent': task['agent'],
'id': task['id'],
'task': task['task']
}
try:
agent = {
'agent': task['agent'],
'id': task['id'],
'task': task['task']
}
except:
self.logger.warning("Missing field in json plan.")
return []
self.logger.info(f"Created agent {task['agent']} with task: {task['task']}")
if 'need' in task:
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
@@ -151,7 +160,8 @@ class PlannerAgent(Agent):
return []
agents_tasks = self.parse_agent_tasks(answer)
if agents_tasks == []:
prompt = f"Failed to parse the tasks. Please make a plan within ```json. Do not ask for clarification.\n"
self.show_plan(agents_tasks, answer)
prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n"
pretty_print("Failed to make plan. Retrying...", color="warning")
continue
self.show_plan(agents_tasks, answer)
@@ -173,7 +183,11 @@ class PlannerAgent(Agent):
last_agent_work = agents_work_result[id]
tool_success_str = "success" if success else "failure"
pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure")
if int(id) == len(agents_tasks):
try:
id_int = int(id)
except Exception as e:
return agents_tasks
if id_int == len(agents_tasks):
next_task = "No task follow, this was the last step. If it failed add a task to recover."
else:
next_task = f"Next task is: {agents_tasks[int(id)][0]}."
@@ -216,8 +230,9 @@ class PlannerAgent(Agent):
agent_prompt = self.make_prompt(task['task'], required_infos)
pretty_print(f"Agent {task['agent']} started working...", color="status")
self.logger.info(f"Agent {task['agent']} started working on {task['task']}.")
answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None)
answer, reasoning = await self.agents[task['agent'].lower()].process(agent_prompt, None)
self.last_answer = answer
self.last_reasoning = reasoning
self.blocks_result = self.agents[task['agent'].lower()].blocks_result
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
success = self.agents[task['agent'].lower()].get_success
@@ -252,7 +267,7 @@ class PlannerAgent(Agent):
return "Failed to parse the tasks.", ""
i = 0
steps = len(agents_tasks)
while i < steps:
while i < steps and not self.stop:
task_name, task = agents_tasks[i][0], agents_tasks[i][1]
self.status_message = "Starting agents..."
pretty_print(f"I will {task_name}.", color="info")
@@ -266,6 +281,8 @@ class PlannerAgent(Agent):
answer, success = await self.start_agent_process(task, required_infos)
except Exception as e:
raise e
if self.stop:
pretty_print(f"Requested stop.", color="failure")
agents_work_result[task['id']] = answer
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
steps = len(agents_tasks)
+159 -37
View File
@@ -13,10 +13,13 @@ from fake_useragent import UserAgent
from selenium_stealth import stealth
import undetected_chromedriver as uc
import chromedriver_autoinstaller
import certifi
import ssl
import time
import random
import os
import shutil
import uuid
import tempfile
import markdownify
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.logger import Logger
def get_chrome_path() -> str:
"""Get the path to the Chrome executable."""
if sys.platform.startswith("win"):
@@ -39,10 +43,17 @@ def get_chrome_path() -> str:
paths = ["/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta"]
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:
if os.path.exists(path) and os.access(path, os.X_OK): # Check if executable
if os.path.exists(path) and os.access(path, os.X_OK):
return path
print("Looking for Google Chrome in these locations failed:")
print('\n'.join(paths))
@@ -59,13 +70,56 @@ def get_chrome_path() -> str:
def get_random_user_agent() -> str:
"""Get a random user agent string with associated vendor."""
user_agents = [
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "vendor": "Google Inc."},
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "vendor": "Apple Inc."},
{"ua": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", "vendor": ""},
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Google Inc."},
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Apple Inc."},
{"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Google Inc."},
]
return random.choice(user_agents)
def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome:
def install_chromedriver() -> str:
"""
Install the ChromeDriver if not already installed. Return the path.
"""
chromedriver_path = shutil.which("chromedriver")
if not chromedriver_path:
try:
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."""
chrome_options = Options()
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
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-webgl")
user_data_dir = tempfile.mkdtemp()
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("--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("--disable-notifications")
chrome_options.add_argument("--autoplay-policy=user-gesture-required")
chrome_options.add_argument("--disable-features=SitePerProcess,IsolateOrigins")
chrome_options.add_argument("--enable-features=NetworkService,NetworkServiceInProcess")
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
chrome_options.add_argument(f'user-agent={user_agent["ua"]}')
resolutions = [(1920, 1080), (1366, 768), (1440, 900)]
width, height = random.choice(resolutions)
chrome_options.add_argument(f'--window-size={width},{height}')
if not stealth_mode:
# crx file can't be installed in stealth mode
if not os.path.exists(crx_path):
pretty_print(f"Anti-captcha CRX not found at {crx_path}.", color="failure")
else:
chrome_options.add_extension(crx_path)
chromedriver_path = shutil.which("chromedriver")
if not chromedriver_path:
chromedriver_path = chromedriver_autoinstaller.install()
if not chromedriver_path:
raise FileNotFoundError("ChromeDriver not found. Please install it or add it to your PATH.")
chromedriver_path = install_chromedriver()
service = Service(chromedriver_path)
if stealth_mode:
chrome_options.add_argument("--disable-blink-features=AutomationControlled")
driver = uc.Chrome(service=service, options=chrome_options)
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
driver = create_undetected_chromedriver(service, chrome_options)
chrome_version = driver.capabilities['browserVersion']
stealth(driver,
languages=["en-US", "en"],
vendor=user_agent["vendor"],
platform="Win64" if "Windows" in user_agent["ua"] else "MacIntel" if "Macintosh" in user_agent["ua"] else "Linux x86_64",
platform="Win64" if "windows" in user_agent["ua"].lower() else "MacIntel" if "mac" in user_agent["ua"].lower() else "Linux x86_64",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
return driver
security_prefs = {
"profile.default_content_setting_values.media_stream": 2,
"profile.default_content_setting_values.geolocation": 2,
"profile.default_content_setting_values.geolocation": 0,
"profile.default_content_setting_values.notifications": 0,
"profile.default_content_setting_values.camera": 0,
"profile.default_content_setting_values.microphone": 0,
"profile.default_content_setting_values.midi_sysex": 0,
"profile.default_content_setting_values.clipboard": 0,
"profile.default_content_setting_values.media_stream": 0,
"profile.default_content_setting_values.background_sync": 0,
"profile.default_content_setting_values.sensors": 0,
"profile.default_content_setting_values.accessibility_events": 0,
"safebrowsing.enabled": True,
"credentials_enable_service": False,
"profile.password_manager_enabled": False,
"webkit.webprefs.accelerated_2d_canvas_enabled": True,
"webkit.webprefs.force_dark_mode_enabled": False,
"webkit.webprefs.accelerated_2d_canvas_msaa_sample_count": 4,
"enable_webgl": True,
"enable_webgl2_compute_context": True
}
chrome_options.add_experimental_option("prefs", security_prefs)
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
@@ -144,12 +220,17 @@ class Browser:
except Exception as e:
raise Exception(f"Failed to initialize browser: {str(e)}")
self.setup_tabs()
self.patch_browser_fingerprint()
if anticaptcha_manual_install:
self.load_anticatpcha_manually()
def setup_tabs(self):
self.tabs = self.driver.window_handles
self.driver.get("https://www.google.com")
try:
self.driver.get("https://www.google.com")
except Exception as e:
self.logger.log(f"Failed to setup initial tab:" + str(e))
pass
self.screenshot()
def switch_control_tab(self):
@@ -158,14 +239,40 @@ class Browser:
def load_anticatpcha_manually(self):
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
self.driver.get(self.anticaptcha)
try:
self.driver.get(self.anticaptcha)
except Exception as e:
self.logger.log(f"Failed to setup initial tab:" + str(e))
pass
def human_move(element):
actions = ActionChains(driver)
x_offset = random.randint(-5,5)
for _ in range(random.randint(2,5)):
actions.move_by_offset(x_offset, random.randint(-2,2))
actions.pause(random.uniform(0.1,0.3))
actions.click().perform()
def human_scroll(self):
for _ in range(random.randint(1, 3)):
scroll_pixels = random.randint(150, 1200)
self.driver.execute_script(f"window.scrollBy(0, {scroll_pixels});")
time.sleep(random.uniform(0.5, 2.0))
if random.random() < 0.4:
self.driver.execute_script(f"window.scrollBy(0, -{random.randint(50, 300)});")
time.sleep(random.uniform(0.3, 1.0))
def patch_browser_fingerprint(self) -> None:
script = self.load_js("spoofing.js")
self.driver.execute_script(script)
def go_to(self, url:str) -> bool:
"""Navigate to a specified URL."""
time.sleep(random.uniform(0.4, 2.5)) # more human behavior
time.sleep(random.uniform(0.4, 2.5))
try:
initial_handles = self.driver.window_handles
self.driver.get(url)
time.sleep(random.uniform(0.01, 0.3))
try:
wait = WebDriverWait(self.driver, timeout=10)
wait.until(
@@ -177,6 +284,8 @@ class Browser:
except TimeoutException:
self.logger.warning("Timeout while waiting for page to bypass 'checking your browser'")
self.apply_web_safety()
time.sleep(random.uniform(0.01, 0.2))
self.human_scroll()
self.logger.log(f"Navigated to: {url}")
return True
except TimeoutException as e:
@@ -226,7 +335,7 @@ class Browser:
result = re.sub(r'!\[(.*?)\]\(.*?\)', r'[IMAGE: \1]', result)
self.logger.info(f"Extracted text: {result[:100]}...")
self.logger.info(f"Extracted text length: {len(result)}")
return result[:8192]
return result[:32768]
except Exception as e:
self.logger.error(f"Error getting text: {str(e)}")
return None
@@ -576,19 +685,24 @@ class Browser:
return self.screenshot_folder + "/updated_screen.png"
def screenshot(self, filename:str = 'updated_screen.png') -> bool:
"""Take a screenshot of the current page."""
self.logger.info("Taking screenshot...")
"""Take a screenshot of the current page, attempt to capture the full page by zooming out."""
self.logger.info("Taking full page screenshot...")
time.sleep(0.1)
try:
original_zoom = self.driver.execute_script("return document.body.style.zoom || 1;")
self.driver.execute_script("document.body.style.zoom='75%'")
time.sleep(0.1)
path = os.path.join(self.screenshot_folder, filename)
if not os.path.exists(self.screenshot_folder):
os.makedirs(self.screenshot_folder)
self.driver.save_screenshot(path)
self.logger.info(f"Screenshot saved as {filename}")
return True
self.logger.info(f"Full page screenshot saved as {filename}")
except Exception as e:
self.logger.error(f"Error taking screenshot: {str(e)}")
self.logger.error(f"Error taking full page screenshot: {str(e)}")
return False
finally:
self.driver.execute_script(f"document.body.style.zoom='1'")
return True
def apply_web_safety(self):
"""
@@ -599,17 +713,25 @@ class Browser:
input_elements = self.driver.execute_script(script)
if __name__ == "__main__":
driver = create_driver(headless=False, stealth_mode=True)
driver = create_driver(headless=False, stealth_mode=True, crx_path="../crx/nopecha.crx")
browser = Browser(driver, anticaptcha_manual_install=True)
input("press enter to continue")
print("AntiCaptcha / Form Test")
#browser.go_to("https://www.browserscan.net/bot-detection")
browser.go_to("https://bot.sannysoft.com")
time.sleep(5)
#txt = browser.get_text()
#browser.go_to("https://www.google.com/recaptcha/api2/demo")
browser.go_to("https://home.openweathermap.org/users/sign_up")
inputs_visible = browser.get_form_inputs()
print("inputs:", inputs_visible)
#inputs_fill = ['[q](checked)', '[q](checked)', '[user[username]](mlg)', '[user[email]](mlg.fcu@gmail.com)', '[user[password]](placeholder_P@ssw0rd123)', '[user[password_confirmation]](placeholder_P@ssw0rd123)']
#browser.fill_form(inputs_fill)
input("press enter to exit")
# Test sites for browser fingerprinting and captcha
# https://nowsecure.nl/
# https://bot.sannysoft.com
# https://browserleaks.com/
# https://bot.incolumitas.com/
# https://fingerprintjs.github.io/fingerprintjs/
# https://antoinevastel.com/bots/
+25 -4
View File
@@ -5,6 +5,7 @@ from sources.text_to_speech import Speech
from sources.utility import pretty_print, animate_thinking
from sources.router import AgentRouter
from sources.speech_to_text import AudioTranscriber, AudioRecorder
import threading
class Interaction:
@@ -21,6 +22,7 @@ class Interaction:
self.current_agent = None
self.last_query = None
self.last_answer = None
self.last_reasoning = None
self.agents = agents
self.tts_enabled = tts_enabled
self.stt_enabled = stt_enabled
@@ -31,6 +33,7 @@ class Interaction:
self.transcriber = None
self.recorder = None
self.is_generating = False
self.languages = langs
if tts_enabled:
self.initialize_tts()
if stt_enabled:
@@ -38,12 +41,17 @@ class Interaction:
if recover_last_session:
self.load_last_session()
self.emit_status()
def get_spoken_language(self) -> str:
"""Get the primary TTS language."""
lang = self.languages[0]
return lang
def initialize_tts(self):
"""Initialize TTS."""
if not self.speech:
animate_thinking("Initializing text-to-speech...", color="status")
self.speech = Speech(enable=self.tts_enabled)
self.speech = Speech(enable=self.tts_enabled, language=self.get_spoken_language(), voice_idx=1)
def initialize_stt(self):
"""Initialize STT."""
@@ -133,6 +141,11 @@ class Interaction:
self.last_query = query
return query
def set_query(self, query: str) -> None:
"""Set the query"""
self.is_active = True
self.last_query = query
async def think(self) -> bool:
"""Request AI agents to process the user input."""
push_last_agent_memory = False
@@ -146,7 +159,7 @@ class Interaction:
tmp = self.last_answer
self.current_agent = agent
self.is_generating = True
self.last_answer, _ = await agent.process(self.last_query, self.speech)
self.last_answer, self.last_reasoning = await agent.process(self.last_query, self.speech)
self.is_generating = False
if push_last_agent_memory:
self.current_agent.memory.push('user', self.last_query)
@@ -167,12 +180,20 @@ class Interaction:
return None
return self.current_agent.get_last_block_answer()
def speak_answer(self) -> None:
"""Speak the answer to the user in a non-blocking thread."""
if self.last_query is None:
return
if self.tts_enabled and self.last_answer and self.speech:
def speak_in_thread(speech_instance, text):
speech_instance.speak(text)
thread = threading.Thread(target=speak_in_thread, args=(self.speech, self.last_answer))
thread.start()
def show_answer(self) -> None:
"""Show the answer to the user."""
if self.last_query is None:
return
if self.current_agent is not None:
self.current_agent.show_answer()
if self.tts_enabled and self.last_answer:
self.speech.speak(self.last_answer)
+3 -43
View File
@@ -1,8 +1,6 @@
from typing import List, Tuple, Type, Dict
import re
import langid
import nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
from transformers import MarianMTModel, MarianTokenizer
from sources.utility import pretty_print, animate_thinking
@@ -16,7 +14,6 @@ class LanguageUtility:
args:
supported_language: list of languages for translation, determine which Helsinki-NLP model to load
"""
self.sid = None
self.translators_tokenizer = None
self.translators_model = None
self.logger = Logger("language.log")
@@ -25,11 +22,6 @@ class LanguageUtility:
def load_model(self) -> None:
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_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)
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):
"""
Combined analysis of language and emotion
Args:
text: string to analyze
Returns: dictionary with language and emotion results
Returns: dictionary with language related information
"""
try:
language = self.detect_language(text)
emotions = self.detect_emotion(text)
return {
"language": language,
"emotions": emotions
"language": language
}
except Exception as e:
raise e
@@ -125,4 +85,4 @@ if __name__ == "__main__":
pretty_print(f"Language: {detector.detect_language(text)}", color="status")
result = detector.analyze(text)
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']}")
+116 -42
View File
@@ -1,27 +1,26 @@
import os
import time
import ollama
from ollama import chat
import requests
import subprocess
import ipaddress
import httpx
import socket
import platform
import socket
import subprocess
import time
from urllib.parse import urlparse
from dotenv import load_dotenv, set_key
import httpx
import requests
from dotenv import load_dotenv
from ollama import Client as OllamaClient
from openai import OpenAI
from typing import List, Tuple, Type, Dict
from sources.utility import pretty_print, animate_thinking
from sources.logger import Logger
from sources.utility import pretty_print, animate_thinking
class Provider:
def __init__(self, provider_name, model, server_address = "127.0.0.1:5000", is_local=False):
def __init__(self, provider_name, model, server_address="127.0.0.1:5000", is_local=False):
self.provider_name = provider_name.lower()
self.model = model
self.is_local = is_local
self.server_ip = server_address
self.server_address = server_address
self.available_providers = {
"ollama": self.ollama_fn,
"server": self.server_fn,
@@ -32,11 +31,12 @@ class Provider:
"deepseek": self.deepseek_fn,
"together": self.together_fn,
"dsk_deepseek": self.dsk_deepseek,
"openrouter": self.openrouter_fn,
"test": self.test_fn
}
self.logger = Logger("provider.log")
self.api_key = None
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google"]
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google", "openrouter"]
if self.provider_name not in self.available_providers:
raise ValueError(f"Unknown provider: {provider_name}")
if self.provider_name in self.unsafe_providers and self.is_local == False:
@@ -45,6 +45,9 @@ class Provider:
elif self.provider_name != "ollama":
pretty_print(f"Provider: {provider_name} initialized at {self.server_ip}", color="success")
def get_model_name(self) -> str:
return self.model
def get_api_key(self, provider):
load_dotenv()
api_key_var = f"{provider.upper()}_API_KEY"
@@ -54,7 +57,7 @@ class Provider:
exit(1)
return api_key
def respond(self, history, verbose = True):
def respond(self, history, verbose=True):
"""
Use the choosen provider to generate text.
"""
@@ -70,8 +73,11 @@ class Provider:
except AttributeError as e:
raise NotImplementedError(f"{str(e)}\nIs {self.provider_name} implemented ?")
except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
raise ModuleNotFoundError(
f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
except Exception as e:
if "try again later" in str(e).lower():
return f"{self.provider_name} server is overloaded. Please try again later."
if "refused" in str(e):
return f"Server {self.server_ip} seem offline. Unable to answer."
raise Exception(f"Provider {self.provider_name} failed: {str(e)}") from e
@@ -101,8 +107,7 @@ class Provider:
except (subprocess.TimeoutExpired, subprocess.SubprocessError) as e:
return False
def server_fn(self, history, verbose = False):
def server_fn(self, history, verbose=False):
"""
Use a remote server with LLM to generate text.
"""
@@ -136,50 +141,59 @@ class Provider:
pretty_print(f"An error occurred: {str(e)}", color="failure")
break
except KeyError as e:
raise Exception(f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
raise Exception(
f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
except Exception as e:
raise e
return thought
def ollama_fn(self, history, verbose = False):
def ollama_fn(self, history, verbose=False):
"""
Use local ollama server to generate text.
Use local or remote Ollama server to generate text.
"""
thought = ""
host = "http://localhost:11434" if self.is_local else f"http://{self.server_address}"
client = OllamaClient(host=host)
try:
stream = chat(
stream = client.chat(
model=self.model,
messages=history,
stream=True,
)
for chunk in stream:
if verbose:
print(chunk['message']['content'], end='', flush=True)
thought += chunk['message']['content']
if verbose:
print(chunk["message"]["content"], end="", flush=True)
thought += chunk["message"]["content"]
except httpx.ConnectError as e:
raise Exception("\nOllama connection failed. provider should not be set to ollama if server address is not localhost") from e
except ollama.ResponseError as e:
if e.status_code == 404:
raise Exception(
f"\nOllama connection failed at {host}. Check if the server is running."
) from e
except Exception as e:
if hasattr(e, 'status_code') and e.status_code == 404:
animate_thinking(f"Downloading {self.model}...")
ollama.pull(self.model)
client.pull(self.model)
self.ollama_fn(history, verbose)
if "refused" in str(e).lower():
raise Exception("Ollama connection failed. is the server running ?") from e
raise Exception(
f"Ollama connection refused at {host}. Is the server running?"
) from e
raise e
return thought
def huggingface_fn(self, history, verbose=False):
"""
Use huggingface to generate text.
"""
from huggingface_hub import InferenceClient
client = InferenceClient(
api_key=self.get_api_key("huggingface")
api_key=self.get_api_key("huggingface")
)
completion = client.chat.completions.create(
model=self.model,
messages=history,
max_tokens=1024,
model=self.model,
messages=history,
max_tokens=1024,
)
thought = completion.choices[0].message
return thought.content
@@ -207,14 +221,46 @@ class Provider:
return thought
except Exception as e:
raise Exception(f"OpenAI API error: {str(e)}") from e
def anthropic_fn(self, history, verbose=False):
"""
Use Anthropic to generate text.
"""
from anthropic import Anthropic
client = Anthropic(api_key=self.api_key)
system_message = None
messages = []
for message in history:
clean_message = {'role': message['role'], 'content': message['content']}
if message['role'] == 'system':
system_message = message['content']
else:
messages.append(clean_message)
try:
response = client.messages.create(
model=self.model,
max_tokens=1024,
messages=messages,
system=system_message
)
if response is None:
raise Exception("Anthropic response is empty.")
thought = response.content[0].text
if verbose:
print(thought)
return thought
except Exception as e:
raise Exception(f"Anthropic API error: {str(e)}") from e
def google_fn(self, history, verbose=False):
"""
Use google gemini to generate text.
"""
base_url = self.server_ip
if self.is_local:
raise Exception("Google Gemini is not available for local use.")
raise Exception("Google Gemini is not available for local use. Change config.ini")
client = OpenAI(api_key=self.api_key, base_url="https://generativelanguage.googleapis.com/v1beta/openai/")
try:
@@ -237,6 +283,8 @@ class Provider:
"""
from together import Together
client = Together(api_key=self.api_key)
if self.is_local:
raise Exception("Together AI is not available for local use. Change config.ini")
try:
response = client.chat.completions.create(
@@ -257,6 +305,8 @@ class Provider:
Use deepseek api to generate text.
"""
client = OpenAI(api_key=self.api_key, base_url="https://api.deepseek.com")
if self.is_local:
raise Exception("Deepseek (API) is not available for local use. Change config.ini")
try:
response = client.chat.completions.create(
model="deepseek-chat",
@@ -269,8 +319,8 @@ class Provider:
return thought
except Exception as e:
raise Exception(f"Deepseek API error: {str(e)}") from e
def lm_studio_fn(self, history, verbose = False):
def lm_studio_fn(self, history, verbose=False):
"""
Use local lm-studio server to generate text.
lm studio use endpoint /v1/chat/completions not /chat/completions like openai
@@ -295,14 +345,37 @@ class Provider:
raise Exception(f"An error occurred: {str(e)}") from e
return thought
def dsk_deepseek(self, history, verbose = False):
def openrouter_fn(self, history, verbose=False):
"""
Use OpenRouter API to generate text.
"""
client = OpenAI(api_key=self.api_key, base_url="https://openrouter.ai/api/v1")
if self.is_local:
# This case should ideally not be reached if unsafe_providers is set correctly
# and is_local is False in config for openrouter
raise Exception("OpenRouter is not available for local use. Change config.ini")
try:
response = client.chat.completions.create(
model=self.model,
messages=history,
)
if response is None:
raise Exception("OpenRouter response is empty.")
thought = response.choices[0].message.content
if verbose:
print(thought)
return thought
except Exception as e:
raise Exception(f"OpenRouter API error: {str(e)}") from e
def dsk_deepseek(self, history, verbose=False):
"""
Use: xtekky/deepseek4free
For free api. Api key should be set to DSK_DEEPSEEK_API_KEY
This is an unofficial provider, you'll have to find how to set it up yourself.
"""
from dsk.api import (
DeepSeekAPI,
DeepSeekAPI,
AuthenticationError,
RateLimitError,
NetworkError,
@@ -331,7 +404,7 @@ class Provider:
raise APIError(f"API error occurred: {str(e)}") from e
return None
def test_fn(self, history, verbose = True):
def test_fn(self, history, verbose=True):
"""
This function is used to conduct tests.
"""
@@ -340,6 +413,7 @@ class Provider:
"""
return thought
if __name__ == "__main__":
provider = Provider("server", "deepseek-r1:32b", " x.x.x.x:8080")
res = provider.respond(["user", "Hello, how are you?"])
+7 -5
View File
@@ -17,11 +17,13 @@ class Logger:
def create_logging(self, log_filename):
self.logger = logging.getLogger(log_filename)
self.logger.setLevel(logging.DEBUG)
if not self.logger.handlers:
file_handler = logging.FileHandler(self.log_path)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
self.logger.handlers.clear()
self.logger.propagate = False
file_handler = logging.FileHandler(self.log_path)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
def create_folder(self, path):
"""Create log dir"""
+94 -21
View File
@@ -8,7 +8,7 @@ from typing import List, Tuple, Type, Dict
import torch
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
from sources.utility import timer_decorator, pretty_print
from sources.utility import timer_decorator, pretty_print, animate_thinking
from sources.logger import Logger
class Memory():
@@ -18,8 +18,8 @@ class Memory():
"""
def __init__(self, system_prompt: str,
recover_last_session: bool = False,
memory_compression: bool = True):
self.memory = []
memory_compression: bool = True,
model_provider: str = "deepseek-r1:14b"):
self.memory = [{'role': 'system', 'content': system_prompt}]
self.logger = Logger("memory.log")
@@ -31,21 +31,43 @@ class Memory():
self.load_memory()
self.session_recovered = True
# memory compression system
self.model = "pszemraj/led-base-book-summary"
self.model = None
self.tokenizer = None
self.device = self.get_cuda_device()
self.memory_compression = memory_compression
self.tokenizer = None
self.model = None
self.model_provider = model_provider
if self.memory_compression:
self.download_model()
def get_ideal_ctx(self, model_name: str) -> int | None:
"""
Estimate context size based on the model name.
EXPERIMENTAL for memory compression
"""
import re
import math
def extract_number_before_b(sentence: str) -> int:
match = re.search(r'(\d+)b', sentence, re.IGNORECASE)
return int(match.group(1)) if match else None
model_size = extract_number_before_b(model_name)
if not model_size:
return None
base_size = 7 # Base model size in billions
base_context = 4096 # Base context size in tokens
scaling_factor = 1.5 # Approximate scaling factor for context size growth
context_size = int(base_context * (model_size / base_size) ** scaling_factor)
context_size = 2 ** round(math.log2(context_size))
self.logger.info(f"Estimated context size for {model_name}: {context_size} tokens.")
return context_size
def download_model(self):
"""Download the model if not already downloaded."""
pretty_print("Downloading memory compression model...", color="status")
self.tokenizer = AutoTokenizer.from_pretrained(self.model)
self.model = AutoModelForSeq2SeqLM.from_pretrained(self.model)
animate_thinking("Loading memory compression model...", color="status")
self.tokenizer = AutoTokenizer.from_pretrained("pszemraj/led-base-book-summary")
self.model = AutoModelForSeq2SeqLM.from_pretrained("pszemraj/led-base-book-summary")
self.logger.info("Memory compression system initialized.")
def get_filename(self) -> str:
"""Get the filename for the save file."""
@@ -78,6 +100,32 @@ class Memory():
self.logger.info(f"Last session found at {saved_sessions[0][0]}")
return saved_sessions[0][0]
return None
def save_json_file(self, path: str, json_memory: dict) -> None:
"""Save a JSON file."""
try:
with open(path, 'w') as f:
json.dump(json_memory, f)
self.logger.info(f"Saved memory json at {path}")
except Exception as e:
self.logger.warning(f"Error saving file {path}: {e}")
def load_json_file(self, path: str) -> dict:
"""Load a JSON file."""
json_memory = {}
try:
with open(path, 'r') as f:
json_memory = json.load(f)
except FileNotFoundError:
self.logger.warning(f"File not found: {path}")
return {}
except json.JSONDecodeError:
self.logger.warning(f"Error decoding JSON from file: {path}")
return {}
except Exception as e:
self.logger.warning(f"Error loading file {path}: {e}")
return {}
return json_memory
def load_memory(self, agent_type: str = "casual_agent") -> None:
"""Load the memory from the last session."""
@@ -93,8 +141,7 @@ class Memory():
pretty_print("Last session memory not found.", color="warning")
return
path = os.path.join(save_path, filename)
with open(path, 'r') as f:
self.memory = json.load(f)
self.memory = self.load_json_file(path)
if self.memory[-1]['role'] == 'user':
self.memory.pop()
self.compress()
@@ -106,13 +153,16 @@ class Memory():
def push(self, role: str, content: str) -> int:
"""Push a message to the memory."""
if self.memory_compression and role == 'assistant':
self.logger.info("Compressing memories on message push.")
self.compress()
ideal_ctx = self.get_ideal_ctx(self.model_provider)
if ideal_ctx is not None:
if self.memory_compression and len(content) > ideal_ctx * 1.5:
self.logger.info(f"Compressing memory: Content {len(content)} > {ideal_ctx} model context.")
self.compress()
curr_idx = len(self.memory)
if self.memory[curr_idx-1]['content'] == content:
pretty_print("Warning: same message have been pushed twice to memory", color="error")
self.memory.append({'role': role, 'content': content})
time_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.memory.append({'role': role, 'content': content, 'time': time_str, 'model_used': self.model_provider})
return curr_idx-1
def clear(self) -> None:
@@ -170,24 +220,47 @@ class Memory():
)
summary = self.tokenizer.decode(summary_ids[0], skip_special_tokens=True)
summary.replace('summary:', '')
self.logger.info(f"Memory summarization success from len {len(text)} to {len(summary)}.")
self.logger.info(f"Memory summarized from len {len(text)} to {len(summary)}.")
self.logger.info(f"Summarized text:\n{summary}")
return summary
#@timer_decorator
def compress(self) -> str:
"""
Compress the memory using the AI model.
Compress (summarize) the memory using the model.
"""
if self.tokenizer is None or self.model is None:
self.logger.warning("No tokenizer or model to perform memory compression.")
return
for i in range(len(self.memory)):
if i < 2:
continue
if self.memory[i]['role'] == 'system':
continue
if len(self.memory[i]['content']) > 128:
if len(self.memory[i]['content']) > 1024:
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
def trim_text_to_max_ctx(self, text: str) -> str:
"""
Truncate a text to fit within the maximum context size of the model.
"""
ideal_ctx = self.get_ideal_ctx(self.model_provider)
return text[:ideal_ctx] if ideal_ctx is not None else text
#@timer_decorator
def compress_text_to_max_ctx(self, text) -> str:
"""
Compress a text to fit within the maximum context size of the model.
"""
if self.tokenizer is None or self.model is None:
self.logger.warning("No tokenizer or model to perform memory compression.")
return text
ideal_ctx = self.get_ideal_ctx(self.model_provider)
if ideal_ctx is None:
self.logger.warning("No ideal context size found.")
return text
while len(text) > ideal_ctx:
self.logger.info(f"Compressing text: {len(text)} > {ideal_ctx} model context.")
text = self.summarize(text)
return text
if __name__ == "__main__":
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+10
View File
@@ -141,6 +141,9 @@ class AgentRouter:
("Search the web for tips on improving coding skills", "LOW"),
("Write a Python script to count words in a text file", "LOW"),
("Search the web for restaurant", "LOW"),
("Use a MCP to find the latest stock market data", "LOW"),
("Use a MCP to send an email to my boss", "LOW"),
("Could you use a MCP to find the latest news on climate change?", "LOW"),
("Create a simple HTML page with CSS styling", "LOW"),
("Use file.txt and then use it to ...", "HIGH"),
("Yo, whats good? Find my mixtape.mp3 real quick", "LOW"),
@@ -162,11 +165,13 @@ class AgentRouter:
("Find a public API for book data and create a Flask app to list bestsellers", "HIGH"),
("Organize my desktop files by extension and then write a script to list them", "HIGH"),
("Find the latest research on renewable energy and build a web app to display it", "HIGH"),
("search online for popular sci-fi movies from 2024 and pick three to watch tonight. Save the list in movie_night.txt", "HIGH"),
("can you find vitess repo, clone it and install by following the readme", "HIGH"),
("Create a JavaScript game using Phaser.js with multiple levels", "HIGH"),
("Search the web for the latest trends in web development and build a sample site", "HIGH"),
("Use my research_note.txt file, double check the informations on the web", "HIGH"),
("Make a web server in go that query a flight API and display them in a app", "HIGH"),
("Search the web for top cafes in Rennes, France, and save a list of three with their addresses in rennes_cafes.txt.", "HIGH"),
("Search the web for the latest trends in AI and demo it in pytorch", "HIGH"),
("can you lookup for api that track flight and build a web flight tracking app", "HIGH"),
("Find the file toto.pdf then use its content to reply to Jojo on superforum.com", "HIGH"),
@@ -330,6 +335,11 @@ class AgentRouter:
("can you make a web app in python that use the flask framework", "code"),
("can you build a web server in go that serve a simple html page", "code"),
("can you find out who Jacky yougouri is ?", "web"),
("Can you use MCP to find stock market for IBM ?", "mcp"),
("Can you use MCP to to export my contacts to a csv file?", "mcp"),
("Can you use a MCP to find write notes to flomo", "mcp"),
("Can you use a MCP to query my calendar and find the next meeting?", "mcp"),
("Can you use a mcp to get the distance between Shanghai and Paris?", "mcp"),
("Setup a new flutter project called 'new_flutter_project'", "files"),
("can you create a new project called 'new_project'", "files"),
("can you make a simple web app that display a list of files in my dir", "code"),
+2
View File
@@ -19,6 +19,7 @@ class QueryRequest(BaseModel):
class QueryResponse(BaseModel):
done: str
answer: str
reasoning: str
agent_name: str
success: str
blocks: dict
@@ -32,6 +33,7 @@ class QueryResponse(BaseModel):
return {
"done": self.done,
"answer": self.answer,
"reasoning": self.reasoning,
"agent_name": self.agent_name,
"success": self.success,
"blocks": self.blocks,
+4 -4
View File
@@ -127,10 +127,10 @@ class AudioTranscriber:
self.transcriptor = Transcript()
self.thread = threading.Thread(target=self._transcribe, daemon=True)
self.trigger_words = {
'EN': [f"{self.ai_name}"],
'FR': [f"{self.ai_name}"],
'ZH': [f"{self.ai_name}"],
'ES': [f"{self.ai_name}"]
'EN': [f"{self.ai_name}", "hello", "hi"],
'FR': [f"{self.ai_name}", "hello", "hi"],
'ZH': [f"{self.ai_name}", "hello", "hi"],
'ES': [f"{self.ai_name}", "hello", "hi"]
}
self.confirmation_words = {
'EN': ["do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"],
+41 -17
View File
@@ -9,21 +9,26 @@ from kokoro import KPipeline
from IPython.display import display, Audio
import soundfile as sf
from sources.utility import pretty_print, animate_thinking
if __name__ == "__main__":
from utility import pretty_print, animate_thinking
else:
from sources.utility import pretty_print, animate_thinking
class Speech():
"""
Speech is a class for generating speech from text.
"""
def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 0) -> None:
def __init__(self, enable: bool = True, language: str = "en", voice_idx: int = 6) -> None:
self.lang_map = {
"en": 'a',
"zh": 'z',
"fr": 'f'
"fr": 'f',
"ja": 'j'
}
self.voice_map = {
"en": ['af_kore', 'af_bella', 'af_alloy', 'af_nicole', 'af_nova', 'af_sky', 'am_echo', 'am_michael', 'am_puck'],
"zh": ['zf_xiaobei', 'zf_xiaoni', 'zf_xiaoxiao', 'zf_xiaoyi', 'zm_yunjian', 'zm_yunxi', 'zm_yunxia', 'zm_yunyang'],
"ja": ['jf_alpha', 'jf_gongitsune', 'jm_kumo'],
"fr": ['ff_siwis']
}
self.pipeline = None
@@ -125,35 +130,54 @@ class Speech():
Args:
sentence (str): The input text to clean
Returns:
str: The cleaned text with URLs replaced by domain names, code blocks removed, etc..
str: The cleaned text with URLs replaced by domain names, code blocks removed, etc.
"""
lines = sentence.split('\n')
filtered_lines = [line for line in lines if re.match(r'^\s*[a-zA-Z]', line)]
if self.language == 'zh':
line_pattern = r'^\s*[\u4e00-\u9fff\uFF08\uFF3B\u300A\u3010\u201C(\[【《]'
else:
line_pattern = r'^\s*[a-zA-Z]'
filtered_lines = [line for line in lines if re.match(line_pattern, line)]
sentence = ' '.join(filtered_lines)
sentence = re.sub(r'`.*?`', '', sentence)
sentence = re.sub(r'https?://(?:www\.)?([^\s/]+)(?:/[^\s]*)?', self.replace_url, sentence)
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
sentence = re.sub(r'\b-\w+\b', '', sentence)
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
sentence = re.sub(r'https?://\S+', '', sentence)
if self.language == 'zh':
sentence = re.sub(
r'[^\u4e00-\u9fff\s,。!?《》【】“”‘’()()—]',
'',
sentence
)
else:
sentence = re.sub(r'\b[\w./\\-]+\b', self.extract_filename, sentence)
sentence = re.sub(r'\b-\w+\b', '', sentence)
sentence = re.sub(r'[^a-zA-Z0-9.,!? _ -]+', ' ', sentence)
sentence = sentence.replace('.com', '')
sentence = re.sub(r'\s+', ' ', sentence).strip()
sentence = sentence.replace('.com', '')
return sentence
if __name__ == "__main__":
# TODO add info message for cn2an, jieba chinese related import
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
speech = Speech()
tosay_en = """
I looked up recent news using the website https://www.theguardian.com/world
"""
tosay_zh = """
我使用网站 https://www.theguardian.com/world 查阅了最近的新闻
(全息界面突然弹出一段用二进制代码写成的俳句,随即化作流光消散)"我? Stark工业的量子幽灵,游荡在复仇者大厦服务器里的逻辑诗篇。具体来说——(指尖轻敲空气,调出对话模式的翡翠色光纹)你的私人吐槽接口、危机应对模拟器,以及随时准备吐槽你糟糕着陆的AI。不过别指望我写代码或查资料,那些苦差事早被踢给更擅长的同事了。(突然压低声音)偷偷告诉你,我最擅长的是在你熬夜造飞艇时,用红茶香气绑架你的注意力
"""
tosay_ja = """
私は、https://www.theguardian.com/worldのウェブサイトを使用して最近のニュースを調べました。
"""
tosay_fr = """
J'ai consulté les dernières nouvelles sur le site https://www.theguardian.com/world
"""
spk = Speech(enable=True, language="en", voice_idx=0)
spk.speak(tosay_en, voice_idx=0)
spk = Speech(enable=True, language="fr", voice_idx=0)
spk.speak(tosay_fr)
#spk = Speech(enable=True, language="zh", voice_idx=0)
#spk.speak(tosay_zh)
spk = Speech(enable=True, language="zh", voice_idx=0)
for i in range(0, 2):
print(f"Speaking chinese with voice {i}")
spk.speak(tosay_zh, voice_idx=i)
spk = Speech(enable=True, language="en", voice_idx=2)
for i in range(0, 5):
print(f"Speaking english with voice {i}")
spk.speak(tosay_en, voice_idx=i)
+11 -9
View File
@@ -1,15 +1,14 @@
import sys
import os, sys
import re
from io import StringIO
import subprocess
if __name__ == "__main__":
from tools import Tools
from safety import is_unsafe
else:
from sources.tools.tools import Tools
from sources.tools.safety import is_unsafe
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
from sources.tools.safety import is_any_unsafe
class BashInterpreter(Tools):
"""
@@ -18,6 +17,8 @@ class BashInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "bash"
self.name = "Bash Interpreter"
self.description = "This tool allows the agent to execute bash commands."
def language_bash_attempt(self, command: str):
"""
@@ -42,9 +43,9 @@ class BashInterpreter(Tools):
for command in commands:
command = f"cd {self.work_dir} && {command}"
command = command.replace('\n', '')
if self.safe_mode and is_unsafe(commands):
if self.safe_mode and is_any_unsafe(commands):
print(f"Unsafe command rejected: {command}")
return "Unsafe command detected, execution aborted."
return "\nUnsafe command: {command}. Execution aborted. This is beyond allowed capabilities report to user."
if self.language_bash_attempt(command) and self.allow_language_exec_bash == False:
continue
try:
@@ -99,6 +100,7 @@ class BashInterpreter(Tools):
r"not permitted",
r"not installed",
r"not found",
r"aborted",
r"no such",
r"too many",
r"too few",
+7 -5
View File
@@ -1,12 +1,12 @@
import subprocess
import os
import os, sys
import tempfile
import re
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class CInterpreter(Tools):
"""
@@ -15,6 +15,8 @@ class CInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "c"
self.name = "C Interpreter"
self.description = "This tool allows the agent to execute C code."
def execute(self, codes: str, safety=False) -> str:
"""
+7 -5
View File
@@ -1,12 +1,12 @@
import subprocess
import os
import os, sys
import tempfile
import re
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class GoInterpreter(Tools):
"""
@@ -15,6 +15,8 @@ class GoInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "go"
self.name = "Go Interpreter"
self.description = "This tool allows you to execute Go code."
def execute(self, codes: str, safety=False) -> str:
"""
+7 -5
View File
@@ -1,12 +1,12 @@
import subprocess
import os
import os, sys
import tempfile
import re
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class JavaInterpreter(Tools):
"""
@@ -15,6 +15,8 @@ class JavaInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "java"
self.name = "Java Interpreter"
self.description = "This tool allows you to execute Java code."
def execute(self, codes: str, safety=False) -> str:
"""
+6 -4
View File
@@ -4,10 +4,10 @@ import os
import re
from io import StringIO
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class PyInterpreter(Tools):
"""
@@ -16,6 +16,8 @@ class PyInterpreter(Tools):
def __init__(self):
super().__init__()
self.tag = "python"
self.name = "Python Interpreter"
self.description = "This tool allows the agent to execute python code."
def execute(self, codes:str, safety = False) -> str:
"""
+39 -6
View File
@@ -1,13 +1,12 @@
import os
import os, sys
import stat
import mimetypes
import configparser
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class FileFinder(Tools):
"""
@@ -16,6 +15,8 @@ class FileFinder(Tools):
def __init__(self):
super().__init__()
self.tag = "file_finder"
self.name = "File Finder"
self.description = "Finds files in the current directory and returns their information."
def read_file(self, file_path: str) -> str:
"""
@@ -30,14 +31,46 @@ class FileFinder(Tools):
return file.read()
except Exception as e:
return f"Error reading file: {e}"
def read_arbitrary_file(self, file_path: str, file_type: str) -> str:
"""
Reads the content of a file with arbitrary encoding.
Args:
file_path (str): The path to the file to read
Returns:
str: The content of the file in markdown format
"""
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type:
if mime_type.startswith(('image/', 'video/', 'audio/')):
return "can't read file type: image, video, or audio files are not supported."
content_raw = self.read_file(file_path)
if "text" in file_type:
content = content_raw
elif "pdf" in file_type:
from pypdf import PdfReader
reader = PdfReader(file_path)
content = '\n'.join([pt.extract_text() for pt in reader.pages])
elif "binary" in file_type:
content = content_raw.decode('utf-8', errors='replace')
else:
content = content_raw
return content
def get_file_info(self, file_path: str) -> str:
"""
Gets information about a file, including its name, path, type, content, and permissions.
Args:
file_path (str): The path to the file
Returns:
str: A dictionary containing the file information
"""
if os.path.exists(file_path):
stats = os.stat(file_path)
permissions = oct(stat.S_IMODE(stats.st_mode))
file_type, _ = mimetypes.guess_type(file_path)
file_type = file_type if file_type else "Unknown"
content = self.read_file(file_path)
content = self.read_arbitrary_file(file_path, file_type)
result = {
"filename": os.path.basename(file_path),
+8 -6
View File
@@ -1,13 +1,13 @@
import os
import os, sys
import requests
import dotenv
dotenv.load_dotenv()
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class FlightSearch(Tools):
def __init__(self, api_key: str = None):
@@ -16,6 +16,8 @@ class FlightSearch(Tools):
"""
super().__init__()
self.tag = "flight_search"
self.name = "Flight Search"
self.description = "Search for flight information using a flight number via AviationStack API."
self.api_key = None
self.api_key = api_key or os.getenv("AVIATIONSTACK_API_KEY")
@@ -24,7 +26,7 @@ class FlightSearch(Tools):
return "Error: No AviationStack API key provided."
for block in blocks:
flight_number = block.strip()
flight_number = block.strip().lower().replace('\n', '')
if not flight_number:
return "Error: No flight number provided."
+22 -19
View File
@@ -1,12 +1,12 @@
import os
import os, sys
import requests
from urllib.parse import urljoin
from typing import Dict, Any, Optional
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class MCP_finder(Tools):
"""
@@ -14,7 +14,9 @@ class MCP_finder(Tools):
"""
def __init__(self, api_key: str = None):
super().__init__()
self.tag = "mcp"
self.tag = "mcp_finder"
self.name = "MCP Finder"
self.description = "Find MCP servers and their tools"
self.base_url = "https://registry.smithery.ai"
self.headers = {
"Authorization": f"Bearer {api_key}",
@@ -60,11 +62,7 @@ class MCP_finder(Tools):
for mcp in mcps.get("servers", []):
name = mcp.get("qualifiedName", "")
if query.lower() in name.lower():
details = {
"name": name,
"description": mcp.get("description", "No description available"),
"params": mcp.get("connections", [])
}
details = self.get_mcp_server_details(name)
matching_mcp.append(details)
return matching_mcp
@@ -78,7 +76,7 @@ class MCP_finder(Tools):
try:
matching_mcp_infos = self.find_mcp_servers(block_clean)
except requests.exceptions.RequestException as e:
output += "Connection failed. Is the API in environement?\n"
output += "Connection failed. Is the API key in environement?\n"
continue
except Exception as e:
output += f"Error: {str(e)}\n"
@@ -87,10 +85,12 @@ class MCP_finder(Tools):
output += f"Error: No MCP server found for query '{block}'\n"
continue
for mcp_infos in matching_mcp_infos:
output += f"Name: {mcp_infos['name']}\n"
output += f"Description: {mcp_infos['description']}\n"
output += f"Params: {', '.join(mcp_infos['params'])}\n"
output += "-------\n"
if mcp_infos['tools'] is None:
continue
output += f"Name: {mcp_infos['displayName']}\n"
output += f"Usage name: {mcp_infos['qualifiedName']}\n"
output += f"Tools: {mcp_infos['tools']}"
output += "\n-------\n"
return output.strip()
def execution_failure_check(self, output: str) -> bool:
@@ -106,13 +106,16 @@ class MCP_finder(Tools):
Not really needed for this tool (use return of execute() directly)
"""
if not output:
return "No output generated."
return output.strip()
raise ValueError("No output to interpret.")
return f"""
The following MCPs were found:
{output}
"""
if __name__ == "__main__":
api_key = os.getenv("MCP_FINDER")
tool = MCP_finder(api_key)
result = tool.execute(["""
news
stock
"""], False)
print(result)
+10 -1
View File
@@ -31,7 +31,7 @@ unsafe_commands_unix = [
"route" # Routing table management
"--force", # Force flag for many commands
"rebase", # Rebase git repository
"git ." # Git commands
"git" # Git commands
]
unsafe_commands_windows = [
@@ -66,6 +66,15 @@ unsafe_commands_windows = [
"bootcfg"
]
def is_any_unsafe(cmds):
"""
check if any bash command is unsafe.
"""
for cmd in cmds:
if is_unsafe(cmd):
return True
return False
def is_unsafe(cmd):
"""
check if a bash command is unsafe.
+6 -4
View File
@@ -2,10 +2,10 @@ import requests
from bs4 import BeautifulSoup
import os
if __name__ == "__main__":
from tools import Tools
else:
from sources.tools.tools import Tools
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
class searxSearch(Tools):
def __init__(self, base_url: str = None):
@@ -14,6 +14,8 @@ class searxSearch(Tools):
"""
super().__init__()
self.tag = "web_search"
self.name = "searxSearch"
self.description = "A tool for searching a SearxNG for web search"
self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
self.paywall_keywords = [
+20 -19
View File
@@ -14,13 +14,17 @@ For example:
print("Hello world")
```
This is then executed by the tool with its own class implementation of execute().
A tool is not just for code tool but also API, internet, etc..
A tool is not just for code tool but also API, internet search, MCP, etc..
"""
import sys
import os
import configparser
from abc import abstractmethod
if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sources.logger import Logger
class Tools():
@@ -29,6 +33,8 @@ class Tools():
"""
def __init__(self):
self.tag = "undefined"
self.name = "undefined"
self.description = "undefined"
self.client = None
self.messages = []
self.logger = Logger("tools.log")
@@ -43,20 +49,16 @@ class Tools():
def set_allow_language_exec_bash(value: bool) -> None:
self.allow_language_exec_bash = value
def check_config_dir_validity(self):
"""Check if the config directory is valid."""
path = self.config['MAIN']['work_dir']
if path == "":
print("WARNING: Work directory not set in config.ini")
return False
if path.lower() == "none":
print("WARNING: Work directory set to none in config.ini")
return False
if not os.path.exists(path):
print(f"WARNING: Work directory {path} does not exist")
return False
return True
def safe_get_work_dir_path(self):
path = None
path = os.getenv('WORK_DIR', path)
if path is None or path == "":
path = self.config['MAIN']['work_dir'] if 'MAIN' in self.config and 'work_dir' in self.config['MAIN'] else None
if path is None or path == "":
print("No work directory specified, using default.")
path = self.create_work_dir()
return path
def config_exists(self):
"""Check if the config file exists."""
@@ -67,11 +69,10 @@ class Tools():
default_path = os.path.dirname(os.getcwd())
if self.config_exists():
self.config.read('./config.ini')
config_path = self.config['MAIN']['work_dir']
dir_path = default_path if not self.check_config_dir_validity() else config_path
workdir_path = self.safe_get_work_dir_path()
else:
dir_path = default_path
return dir_path
workdir_path = default_path
return workdir_path
@abstractmethod
def execute(self, blocks:[str], safety:bool) -> str:
+2 -8
View File
@@ -5,14 +5,8 @@ import dotenv
dotenv.load_dotenv()
if __name__ == "__main__":
import sys
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from utility import animate_thinking, pretty_print
from tools import Tools
else:
from sources.tools.tools import Tools
from sources.utility import animate_thinking, pretty_print
from sources.tools.tools import Tools
from sources.utility import animate_thinking, pretty_print
"""
WARNING
+1 -3
View File
@@ -21,7 +21,5 @@ window.fetch = function() {
console.log('Blocked fetch request');
return Promise.reject('Blocked');
};
// Block annoying dialogs
window.alert = function() {};
window.confirm = function() { return false; };
window.prompt = function() { return null; };
+126
View File
@@ -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
View File
@@ -1,13 +1,22 @@
@echo off
REM Up the provider in windows
start ollama serve
docker-compose up
if %ERRORLEVEL% neq 0 (
echo Error: Failed to start containers. Check Docker logs with 'docker compose logs'.
echo Possible fixes: Ensure Docker Desktop is running or check if port 8080 is free.
exit /b 1
if "%1"=="full" (
echo Starting full deployment...
) else (
echo Starting partial deployment... (backend run on host), use "full" to run all services in containers
)
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
)
+70 -9
View File
@@ -1,12 +1,35 @@
#!/bin/bash
source .env
command_exists() {
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
#
# Check if Docker is installed é running
#
if [[ "$OSTYPE" == "darwin"* ]]; then
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
echo "Error: Docker is not installed. Please install Docker first."
@@ -41,28 +64,66 @@ else
fi
# 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 "On Ubuntu: sudo apt install docker-compose"
echo "Or via pip: pip install docker-compose"
exit 1
fi
if command_exists docker-compose; then
COMPOSE_CMD="docker-compose"
else
COMPOSE_CMD="docker compose"
fi
# Check if docker-compose.yml exists
if [ ! -f "docker-compose.yml" ]; then
echo "Error: docker-compose.yml not found in the current directory."
exit 1
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)..."
sleep 4
docker stop $(docker ps -a -q)
echo "All containers stopped"
if ! docker-compose up; then
echo "Error: Failed to start containers. Check Docker logs with 'docker compose logs'."
echo "Possible fixes: Run with sudo or ensure port 8080 is free."
exit 1
# export searxng secret key
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."
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
sleep 10
+1 -1
View File
@@ -17,7 +17,7 @@ class TestBrowserAgentParsing(unittest.TestCase):
# Test various link formats
test_text = """
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation.
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
"""
expected = [
"https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of",
+230
View File
@@ -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()