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
43 changed files with 2718 additions and 3430 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
+9
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'
OPENROUTER_API_KEY='xxxxx'
TOGETHER_API_KEY='xxxxx'
GOOGLE_API_KEY='xxxxx'
ANTHROPIC_API_KEY='xxxxx'
+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_*
+65 -9
View File
@@ -1,10 +1,31 @@
FROM ubuntu:22.04
# Warning: doesn't work yet, backend is run on host machine for now
WORKDIR /app
FROM --platform=linux/amd64 python:3.11-slim
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update -qq -y && \
apt-get install -y \
# 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 \
@@ -24,23 +45,58 @@ apt-get install -y \
libgtk-4-1 \
libnss3 \
xdg-utils \
wget && \
wget \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get update -y && \
apt-get install -y \
alsa-utils \
&& rm -rf /var/lib/apt/lists/*
ENV CHROME_TESTING_VERSION=134.0.6998.88
ENV DISPLAY=:99
WORKDIR /app
RUN set -eux; \
wget -qO /tmp/chrome.zip \
"https://storage.googleapis.com/chrome-for-testing-public/${CHROME_TESTING_VERSION}/linux64/chrome-linux64.zip"; \
unzip -q /tmp/chrome.zip -d /opt; \
rm /tmp/chrome.zip; \
ln -s /opt/chrome-linux64/chrome /usr/local/bin/google-chrome; \
ln -s /opt/chrome-linux64/chrome /usr/local/bin/chrome; \
mkdir -p /opt/chrome; \
ln -s /opt/chrome-linux64/chrome /opt/chrome/chrome; \
google-chrome --version
RUN set -eux; \
wget -qO /tmp/chromedriver.zip \
"https://storage.googleapis.com/chrome-for-testing-public/${CHROME_TESTING_VERSION}/linux64/chromedriver-linux64.zip"; \
unzip -q /tmp/chromedriver.zip -d /tmp; \
mv /tmp/chromedriver-linux64/chromedriver /usr/local/bin; \
rm /tmp/chromedriver.zip; \
chmod +x /usr/local/bin/chromedriver; \
chromedriver --version
RUN chmod +x /opt/chrome/chrome
# 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
+27 -16
View File
@@ -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.
@@ -60,9 +64,9 @@ source agentic_seek_env/bin/activate
Ensure Python, Docker and docker compose, and Google chrome are installed.
We recommand Python 3.10.0.
We recommend Python 3.10.0.
**Automatic Installation (Recommanded):**
**Automatic Installation (recommended):**
For Linux/Macos:
```sh
@@ -120,8 +124,9 @@ Install requirements: `pip3 install -r requirements.txt`
## Setup for running LLM locally on your machine
**We recommend using at the very least Deepseek 14B, smaller models will struggle with tasks especially for web browsing.**
**Hardware Requirements:**
To run LLMs locally, you'll need sufficient hardware. At a minimum, a GPU capable of running Qwen/Deepseek 14B is required. See the FAQ for detailed model/performance recommendations.
**Setup your local provider**
@@ -135,7 +140,7 @@ 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 a LLM supported by your provider. We recommand reasoning model such as *Qwen* or *Deepseek*.
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*.
See the **FAQ** at the end of the README for required hardware.
@@ -154,10 +159,12 @@ work_dir = /Users/mlg/Documents/workspace # The workspace for AgenticSeek.
jarvis_personality = False # Whenever to use a more "Jarvis" like personality (experimental)
languages = en zh # The list of languages, Text to speech will default to the first language on the list
[BROWSER]
headless_browser = True # Whenever to use headless browser, recommanded only if you use web interface.
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**
@@ -200,14 +207,14 @@ Example: export `TOGETHER_API_KEY="xxxxx"`
| Provider | Local? | Description |
|-----------|--------|-----------------------------------------------------------|
| openai | Depends | Use ChatGPT API |
| deepseek-api | No | Deepseek API (non-private) |
| deepseek | No | Deepseek API (non-private) |
| huggingface| No | Hugging-Face API (non-private) |
| togetherAI | No | Use together AI API (non-private) |
| google | No | Use google gemini API (non-private) |
*We advice against using gpt-4o or other closedAI models*, performance are poor for web browsing and task planning.
*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 seem to ignore our prompt for format to respect, which are optimized for deepseek r1.
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)
@@ -240,7 +247,7 @@ start ./start_services.cmd # Window
python3 cli.py
```
We advice you set `headless_browser` to False in the config.ini for CLI mode.
We advise you set `headless_browser` to False in the config.ini for CLI mode.
**Options 2:** Run with the Web interface.
@@ -314,7 +321,7 @@ Clone the repository and enter the `server/`folder.
```sh
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
cd agenticSeek/server/
cd agenticSeek/llm_server/
```
Install server specific requirements:
@@ -534,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
@@ -547,8 +554,12 @@ Were looking for developers to improve AgenticSeek! Check out open issues or
## Maintainers:
> [Fosowl](https://github.com/Fosowl) | Paris Time | (Sometime busy)
> [Fosowl](https://github.com/Fosowl) | Paris Time
> [https://github.com/antoineVIVIES](antoineVIVIES) | Taipei Time | (Often busy)
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
> [steveh8758](https://github.com/steveh8758) | Taipei Time | (Always busy)
> [steveh8758](https://github.com/steveh8758) | Taipei Time
## Special Thanks:
> [tcsenpai](https://github.com/tcsenpai) For dockerization of backend
+7 -3
View File
@@ -34,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️⃣ **复制储存库与设置环境变数**
@@ -149,6 +151,8 @@ headless_browser = True # 是否使用无头浏览器,只有在使用网页界
stealth_mode = True # 使用无法检测的 selenium 来减少浏览器检测
```
警告:使用 LM-studio 运行 LLM 时,请*不要*将 provider_name 设置为 `openai`。请将其设置为 `lm-studio`
注意:某些提供者(如 lm-studio)需要在 IP 前面加上 `http://`。例如 `http://127.0.0.1:1234`
@@ -560,8 +564,8 @@ DeepSeek R1 天生会说中文
## 维护者:
> [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙)
> [Fosowl](https://github.com/Fosowl) | 巴黎时间
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | (经常很忙)
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
> [steveh8758](https://github.com/steveh8758) | 台北时间 | (总是很忙)
> [steveh8758](https://github.com/steveh8758) | 台北时间
+55 -42
View File
@@ -8,23 +8,23 @@
[English](./README.md) | 繁體中文 | [日本語](./README_JP.md)
*一个 **100% 本地替代 Manus AI** 的方案,款支持音的 AI 助理能够自主浏览网页、编代码和规划任务,同时所有数据保留在您的设备上。专为本地推理模型量身打造,完全在您自己的硬件上运行,确保完全的隐私保护和零端依。*
*一个 **100% 本地替代 Manus AI** 的方案,款支持音的 AI 助理能够自主瀏覽網頁、编代码和規劃任務,同时所有用戶資料保留在您的裝置上。專門為本地推理模型量身打造,完全在您自己的硬體上執行,确保完全的隐私保护和零端依。*
[![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)
### 为什么选择 AgenticSeek
### 为什么選擇 AgenticSeek
* 🔒 完全本地化隐私保护 - 所有功能都在您的设备上行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
* 🔒 完全本地化隐私保护 - 所有功能都在您的设备上行 — 无云端服务,无数据共享。您的文件、对话和搜索始终保持私密。
* 🌐 智能网页浏览 - AgenticSeek 能够自主浏览互联网 — 搜索、读、提取信息、填写网页表 — 全程无需人工操作。
* 🌐 智能網頁瀏覽 - AgenticSeek 能够自主瀏覽網頁 — 搜索、读、提取信息、填寫網页表 — 全程无需人工操作。
* 💻 自主编码助手 - 需要代码?它可以编、调试并行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
* 💻 自主编码助手 - 需要代码?它可以编、调试并行 Python、C、Go、Java 等多种语言的程序 — 全程无需监督。
* 🧠 智能代理选择 - 您提问,它自动选择最适合该任务的代理。就像拥有一个随时待命的家团队。
* 🧠 智能代理选择 - 您提问,它自动选择最适合该任务的代理。就像拥有一个随时待命的家团队。
* 📋 规划执行复杂任务 - 从旅行规划到复杂项目 — 它能大型任务分解为步骤,并利用多个 AI 代理完成工作。
* 📋 规划执行复杂任务 - 从旅行规划到复杂项目 — 它能大型任务分解为步骤,并利用多个 AI 代理完成工作。
* 🎙️ 音功能 - 清晰、快速、未来感十足的语音与语音转文本功能,您能像科幻电影中一样您的个人 AI 助手对话。
* 🎙️ 音功能 - 清晰、快速、未来感十足的語音與語音轉文本功能,您能像科幻电影中一样您的个人 AI 助手对话。
https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
@@ -36,6 +36,8 @@ https://github.com/user-attachments/assets/4bd5faf6-459f-4f94-bd1d-238c4b331469
確保已安裝了 Chrome driverDocker 和 Python 3.10(或更新)。
我们强烈建议您使用 Python 3.10 進行設定,否则可能會发生依赖错误。
有關於 Chrome driver 的問題,請參見 **Chromedriver** 部分。
### 1️⃣ **複製儲存庫與設置環境變數**
@@ -62,7 +64,7 @@ source agentic_seek_env/bin/activate
./install.sh
```
** 若要让文本转语音(TTS)功能支持中文,你需要安装 jieba(中文分词库)和 cn2an(中文数字转换库):**
** 若要將文字轉成語音(TTS)功能支持中文,你需要安装 jieba(中文分詞庫)和 cn2an(中文數字轉換庫):**
```
pip3 install jieba cn2an
@@ -71,7 +73,7 @@ pip3 install jieba cn2an
**手動安裝:**
**注意:对于任何操作系统,请确保您安装的 ChromeDriver 您已安装的 Chrome 版本匹配。运行 `google-chrome --version`。如果您的 Chrome 版本 > 135请参阅已知问题**
**注意:對於不同作業系統,請確保已經安装的 ChromeDriver 您已安装的 Chrome 版本一致。可以執行 `google-chrome --version`。如果您的 Chrome 版本 > 135請參考已知问题**
- *Linux*:
@@ -79,7 +81,7 @@ pip3 install jieba cn2an
安装依赖项:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
安装您的 Chrome 浏览器版本匹配的 ChromeDriver
安装您的 Chrome 瀏覽器版本匹配的 ChromeDriver
`sudo apt install -y chromium-chromedriver`
安装 requirements`pip3 install -r requirements.txt`
@@ -102,11 +104,11 @@ pip3 install jieba cn2an
安装 pyreadline3`pip install pyreadline3`
手动安装 portaudio(例如,通过 vcpkg 或预编译的二制文件),然后运行:`pip install pyaudio`
手动安装 portaudio(例如,通过 vcpkg 或預編譯的二制文件),然後運行:`pip install pyaudio`
从以下址手动下载并安装 chromedriverhttps://sites.google.com/chromium.org/driver/getting-started
从以下址手动下载并安装 chromedriverhttps://sites.google.com/chromium.org/driver/getting-started
chromedriver 放置在包含在您的 PATH 中的目录中。
chromedriver 放置在包含在您的 PATH 中的目录中。
安装 requirements`pip3 install -r requirements.txt`
@@ -114,34 +116,45 @@ pip3 install jieba cn2an
**建議至少使用 Deepseek 14B 以上參數的模型,較小的模型難以使用助理功能並且很快就會忘記上下文之間的關係。**
**本地行助手**
**本地行助手**
启动你的本地提供者,例如使用 ollama:
啟動你的本地提供者,例如使用 ollama:
```sh
ollama serve
```
请参下方支持的本地提供者列表。
请参下方支持的本地提供者列表。
修改 `config.ini` 文件,将 `provider_name` 设置为支持的提供者,并`provider_model` 设置为 `deepseek-r1:14b`
修改 config.ini 文件以設定 provider_name 为支持的提供者,并provider_model 設定为该提供者支持的 LLM。我们推荐使用具有推理能力的模型,如 *Qwen**Deepseek*
注意:`deepseek-r1:14b` 只是一个示例,如果你的硬件允许,可以使用更大的模型
请参见 README 末尾的 **FAQ** 部分了解所需硬件
```sh
[MAIN]
is_local = True
provider_name = ollama # 或 lm-studio, openai 等
provider_model = deepseek-r1:14b
is_local = True # 无论是在本地運行还是使用远程提供者。
provider_name = ollama # 或 lm-studio, openai 等..
provider_model = deepseek-r1:14b # 选择适合您硬件的模型
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # 您的 AI 助手的名称
recover_last_session = True # 是否恢复之前的會话
save_session = True # 是否记住当前會话
speak = True # 文本轉語音
listen = False # 語音轉文本,僅适用于命令行界面
work_dir = /Users/mlg/Documents/workspace # AgenticSeek 的工作空间。
jarvis_personality = False # 是否使用更"贾维斯"风格的性格,不推荐在小型模型上使用
languages = en zh # 语言列表,文本轉語音將默认使用列表中的第一种语言
[BROWSER]
headless_browser = True # 是否使用无头瀏覽器,只有在使用網页界面时才推荐使用。
stealth_mode = True # 使用无法檢測的 selenium 来减少瀏覽器檢測
```
**本地提供者列表**
| 提供者 | 本地? | 描述 |
|-------------|--------|-------------------------------------------------------|
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地行 LLM |
| lm-studio | 是 | 使用 LM Studio 本地行 LLM `provider_name` 设置`lm-studio`|
| ollama | 是 | 使用 ollama 作为 LLM 提供者,轻松本地行 LLM |
| lm-studio | 是 | 使用 LM Studio 本地行 LLM `provider_name` 設定`lm-studio`|
| openai | 否 | 使用兼容的 API |
下一步: [Start services and run AgenticSeek](#Start-services-and-Run)
@@ -171,14 +184,14 @@ provider_server_address = 127.0.0.1:5000
---
## Start services and Run
(启动服务并行)
(啟動服务并行)
如果需要,请激活你的 Python 环境。
```sh
source agentic_seek_env/bin/activate
```
启动所需的服务。这将启动 `docker-compose.yml` 中的所有服务,包括:
啟動所需的服务。这將啟動 `docker-compose.yml` 中的所有服务,包括:
- searxng
- redis(由 redis 提供支持)
- 前端
@@ -188,25 +201,25 @@ sudo ./start_services.sh # MacOS
start ./start_services.cmd # Windows
```
**选项 1:** 使用 CLI 界面行。
**選項 1:** 使用 CLI 界面行。
```sh
python3 cli.py
```
**选项 2:** 使用 Web 界面行。
**選項 2:** 使用 Web 界面行。
注意:目前我們建議您使用 CLI 界面。Web 界面仍在積極開發中。
启动后端服务。
啟動後端服务。
```sh
python3 api.py
```
访问 `http://localhost:3000/`,你应该看到 Web 界面。
访问 `http://localhost:3000/`,你应该看到 Web 界面。
请注意,目前 Web 界面不支持消息流式传输
请注意,目前 Web 界面不支持消息流式傳輸
*如果你不知道如何開始,請參閱 **Usage** 部分*
@@ -215,9 +228,9 @@ python3 api.py
## Usage (使用方法)
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中设置语言选项
为确保 agenticSeek 在中文环境下正常工作,请确保在 config.ini 中設定语言選項
languages = en zh
更多信息请参 Config 部分
更多信息请参 Config 部分
確定所有的核心檔案都啟用了,也就是執行過這條命令 `./start_services.sh` 然後你就可以使用 `python3 cli.py` 來啟動 AgenticSeek 了!
@@ -274,11 +287,11 @@ python3 cli.py
所以我們希望你在使用時,能明確地表明你希望他要怎麼做,下面給你一個範例!
你該說:
- 进行网络搜索,找出哪些国家最适合自旅行
- 進行網路搜索,找出哪些国家最适合自旅行
而不是說:
- 你知道哪些国家适合自旅行?
- 你知道哪些国家适合自旅行?
---
@@ -344,7 +357,7 @@ provider_server_address = x.x.x.x:3333
## 語音轉文字
请注意,目前语音转文字功能仅支持英语。
请注意,目前語音轉文字功能僅支援英语。
預設狀況下,語音轉文字功能是停用的。若要啟用它,請在 `config.ini` 檔案中,將 `listen` 選項設為 `True`
@@ -525,13 +538,13 @@ https://googlechromelabs.github.io/chrome-for-testing/
**Q: 是否支持中文以外的语言?**
DeepSeek R1 天生说中文
DeepSeek R1 天生说中文
但注意:代理路由系统只懂英文,所以必须通过 config.ini 的 languages 参数(如 languages = en zh)告诉系统:
如果不设置中文?果可能是:你让它写代码,结果跳出来个"生代理"(虽然我们根本没有这个代理... 但系统一脸懵圈!)
如果不設定中文?果可能是:你讓它寫代码,结果跳出来个"生代理"(虽然我们根本没有这个代理... 但系统一脸懵圈!)
实际上下载一个小型翻译模型来协助任务分配
实际上下载一个小型翻译模型来协助任务分配
## 貢獻
@@ -543,8 +556,8 @@ DeepSeek R1 天生会说中文
## 维护者:
> [Fosowl](https://github.com/Fosowl) | 巴黎时间 | (有时很忙)
> [Fosowl](https://github.com/Fosowl) | 巴黎時間 | (有时很忙)
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北时间 | 常很忙)
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES) | 台北時間 | 常很忙)
> [steveh8758](https://github.com/steveh8758) | 台北时间 | 是很忙)
> [steveh8758](https://github.com/steveh8758) | 台北時間 | 是很忙)
+4 -3
View File
@@ -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.
@@ -494,4 +495,4 @@ Nous recherchons des développeurs pour améliorer AgenticSeek ! Consultez la se
## Mainteneurs:
> [Fosowl](https://github.com/Fosowl)
> [steveh8758](https://github.com/steveh8758)
> [https://github.com/antoineVIVIES](https://github.com/antoineVIVIES)
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Time
+301 -219
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) | 日本語
# 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/8hGDaME3TC) [![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,7 +50,7 @@ cd agenticSeek
mv .env.example .env
```
### 2 **仮想環境作成**
### 2 **仮想環境作成**
```sh
python3 -m venv agentic_seek_env
@@ -71,99 +58,176 @@ source agentic_seek_env/bin/activate
# Windowsの場合: agentic_seek_env\Scripts\activate
```
### 3️⃣ **パッケージインストール**
### 3️⃣ **パッケージインストール**
**自動インストール:**
Python、Dockerとdocker compose、Google Chromeがインストールされていることを確認してください。
Python 3.10.0を推奨します。
**自動インストール(推奨):**
Linux/Macosの場合:
```sh
./install.sh
```
** テキスト読み上げ(TTS)機能で日本語をサポートするには、fugashi(日本語分かち書きライブラリ)をインストールする必要があります:**
** 注意: 日本語のテキスト読み上げ(TTS)機能には多くの依存関係が必要で、問題が発生する可能性があります。mecabrcに関する問題が発生することがあります。現在のところ、この問題を修正する方法が見つかっていません。当面は日本語でのテキスト読み上げ機能を無効にすることをお勧めします。**
必要なライブラリをインストールする場合は以下のコマンドを実行してください:
```
pip3 install --upgrade pyopenjtalk jaconv mojimoji unidic fugashi
pip install unidic-lite
python -m unidic download
```
**手動で:**
Windowsの場合:
```sh
pip3 install -r requirements.txt
# または
python3 setup.py install
./install.bat
```
**手動:**
**注意:どのOSでも、インストールするChromeDriverがインストール済みのChromeバージョンと一致していることを確認してください。`google-chrome --version`を実行してください。Chrome >135の場合の既知の問題を参照してください。**
- *Linux*:
パッケージリストの更新:`sudo apt update`
依存関係のインストール:`sudo apt install -y alsa-utils portaudio19-dev python3-pyaudio libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1`
Chromeブラウザのバージョンに一致するChromeDriverのインストール:
`sudo apt install -y chromium-chromedriver`
要件のインストール:`pip3 install -r requirements.txt`
- *Macos*:
brewの更新:`brew update`
chromedriverのインストール:`brew install --cask chromedriver`
portaudioのインストール:`brew install portaudio`
pipのアップグレード:`python3 -m pip install --upgrade pip`
wheelのアップグレード:`pip3 install --upgrade setuptools wheel`
要件のインストール:`pip3 install -r requirements.txt`
- *Windows*:
pyreadline3のインストール:`pip install pyreadline3`
portaudioの手動インストール(例:vcpkgまたはビルド済みバイナリ経由)後、実行:`pip install pyaudio`
chromedriverの手動ダウンロードとインストール:https://sites.google.com/chromium.org/driver/getting-started
PATHに含まれるディレクトリにchromedriverを配置します。
要件のインストール:`pip3 install -r requirements.txt`
---
## ローカルマシンでLLMを実行するためのセットアップ
## マシン上でローカルにLLMを実行するためのセットアップ
**少なくともDeepseek 14B使用することをお勧めします。小さモデルは、特にウェブブラウジングのタスクで苦労する可能性があります。**
**少なくともDeepseek 14B使用を推奨します。より小さモデルは、特にウェブブラウジングのタスクで苦労ます。**
**ローカルプロバイダーをセットアップする**
たとえば、ollamaを使用してローカルプロバイダーを開始します:
**ローカルプロバイダーのセットアップ**
ローカルプロバイダーを開始します。例えばollamaの場合:
```sh
ollama serve
```
以下に、サポートされているローカルプロバイダーのリストを示します
サポートされているローカルプロバイダーのリストについては、以下を参照してください
**config.ini更新する**
**config.ini更新**
config.iniファイルを変更して、`provider_name`をサポートされているプロバイダーに設定し、`provider_model``deepseek-r1:14b`に設定します。
config.iniファイルを変更して、provider_nameをサポートされているプロバイダーにprovider_modelをプロバイダーがサポートするLLMに設定します。*Qwen*や*Deepseek*などの推論モデルを推奨します。
注意: `deepseek-r1:14b`は例です。ハードウェアが許可する場合は、より大きなモデルを使用してください。
必要なハードウェアについては、READMEの最後にある**FAQ**を参照してください。
```sh
[MAIN]
is_local = True
provider_name = ollama # または lm-studio、openai など
provider_model = deepseek-r1:14b
is_local = True # ローカルで実行するか、リモートプロバイダーで実行するか。
provider_name = ollama # またはlm-studio、openaiなど
provider_model = deepseek-r1:14b # ハードウェアに合ったモデルを選択してください
provider_server_address = 127.0.0.1:11434
agent_name = Jarvis # AIの名前
recover_last_session = True # 前のセッションを復元するかどうか
save_session = True # 現在のセッションを記憶するかどうか
speak = True # テキスト読み上げ
listen = False # 音声認識、CLIのみ
work_dir = /Users/mlg/Documents/workspace # AgenticSeekのワークスペース。
jarvis_personality = False # より「Jarvis」らしい性格を使用するかどうか(実験的)
languages = en zh # 言語のリスト、テキスト読み上げはリストの最初の言語にデフォルト設定されます
[BROWSER]
headless_browser = True # ヘッドレスブラウザを使用するかどうか、ウェブインターフェースを使用する場合のみ推奨。
stealth_mode = True # undetected seleniumを使用してブラウザ検出を減らす
```
警告:LM-studioを使用してLLMを実行する場合、provider_nameを`openai`に設定しないでください。`lm-studio`に設定してください。
注意:一部のプロバイダー(例:lm-studio)では、IPの前に`http://`が必要です。例:`http://127.0.0.1:1234`
**ローカルプロバイダーのリスト**
| プロバイダー | ローカル? | 説明 |
| プロバイダー | ローカル | 説明 |
|-----------|--------|-----------------------------------------------------------|
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
| lm-studio | はい | LM studioを使用してローカルでLLMを実行`provider_name``lm-studio`に設定)|
| openai | はい | OpenAI互換APIを使用 |
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
| lm-studio | はい | LM studioでLLMをローカル実行します`provider_name``lm-studio`に設定)|
| openai | はい | openai互換API(例:llama.cppサーバー)を使用します |
次のステップ: [サービス開始してAgenticSeek実行する](#Start-services-and-Run)
次のステップ[サービス開始AgenticSeek実行](#サービスの開始と実行)
*問題が発生している場合は、**既知の問題**セクションを参照してください*
*問題が発生し場合は、**既知の問題**セクションを参照してください*
*ハードウェアがDeepseekをローカルで実行できない場合は、**APIを使用した実行**セクションを参照してください*
*ハードウェアがローカルでdeepseekを実行できない場合は、**API実行**セクションを参照してください*
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
---
## APIを使用したセットアップ
## APIで実行するためのセットアップ
`config.ini`希望するプロバイダーを設定してください。
`config.ini`目的のプロバイダーを設定します。APIプロバイダーのリストについては、以下を参照してください。
```sh
[MAIN]
is_local = False
provider_name = openai
provider_model = gpt-4o
provider_server_address = 127.0.0.1:5000
provider_name = google
provider_model = gemini-2.0-flash
provider_server_address = 127.0.0.1:5000 # 関係ありません
```
警告:設定に末尾のスペースがないことを確認してください。
警告: `config.ini`に末尾のスペースがないことを確認してください。
APIキーをエクスポートします:`export <<PROVIDER>>_API_KEY="xxx"`
ローカルのOpenAIベースのAPIを使用する場合は、`is_local`をTrueに設定してください。
例:`export TOGETHER_API_KEY="xxxxx"`
OpenAIベースのAPIが独自のサーバーで実行されている場合は、IPアドレスを変更してください。
**APIプロバイダーのリスト**
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
| プロバイダー | ローカル? | 説明 |
|-----------|--------|-----------------------------------------------------------|
| openai | 場合による | ChatGPT APIを使用 |
| deepseek-api | いいえ | Deepseek API(非プライベート) |
| huggingface| いいえ | Hugging-Face API(非プライベート) |
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
| google | いいえ | google gemini APIを使用(非プライベート) |
*問題が発生している場合は、**既知の問題**セクションを参照してください。*
*gpt-4oや他のclosedAIモデルの使用は推奨しません*。ウェブブラウジングやタスク計画のパフォーマンスが悪いです。
また、geminiではコーディング/bashが失敗する可能性があることに注意してください。deepseek r1用に最適化されたフォーマットのプロンプトを無視するようです。
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
*問題が発生した場合は、**既知の問題**セクションを参照してください*
*詳細な設定ファイルの説明については、**設定**セクションを参照してください。*
@@ -171,30 +235,30 @@ OpenAIベースのAPIが独自のサーバーで実行されている場合は
## サービスの開始と実行
必要に応じてPython環境をアクティブにしてください
必要に応じてPython環境をアクティブ化します
```sh
source agentic_seek_env/bin/activate
```
必要なサービスを開始します。これにより、docker-compose.ymlから以下のサービスがすべて開始されます:
- 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インターフェースで実行します。
バックエンドを開始します。
@@ -204,113 +268,89 @@ python3 api.py
`http://localhost:3000/`にアクセスすると、Webインターフェースが表示されます。
現在、Webインターフェースではメッセージのストリーミングがサポートされていないことに注意してください。
---
## 使用方法
`./start_services.sh`でサービスが起動していることを確認し、CLIモードの場合は`python3 cli.py`で、Webインターフェースの場合は`python3 api.py`を実行してから`localhost:3000`にアクセスしてAgenticSeekを実行します。
設定で`listen = True`を設定することで、音声認識を使用することもできます。CLIモードのみ。
終了するには、単に`goodbye`と発言/入力します。
以下に使用例をいくつか示します:
> *Pythonでスネークゲームを作って!*
> *フランスのレンヌでトップのカフェをウェブ検索し、3つのカフェのリストとその住所をrennes_cafes.txtに保存して。*
> *数値の階乗を計算するGoプログラムを書いて、それをfactorial.goとしてワークスペースに保存して。*
> *summer_picturesフォルダ内のすべてのJPGファイルを検索し、今日の日付で名前を変更し、名前変更されたファイルのリストをphotos_list.txtに保存して。*
> *2024年の人気のSF映画をオンラインで検索し、今夜観る映画を3つ選んで。リストをmovie_night.txtに保存して。*
> *2025年の最新AIニュース記事をウェブで検索し、3つ選択して、それらのタイトルと要約をスクレイピングするPythonスクリプトを書いて。スクリプトをnews_scraper.pyとして、要約を/home/projectsのai_news.txtに保存して。*
> *金曜日、無料の株価APIをウェブで検索し、supersuper7434567@gmail.comで登録し、そのAPIを使用してテスラの日々の価格を取得するPythonスクリプトを書いて、結果をstock_prices.csvに保存して。*
*フォーム入力機能はまだ実験的であり、失敗する可能性があることに注意してください。*
クエリを入力すると、AgenticSeekはタスクに最適なエージェントを割り当てます。
これは初期のプロトタイプであるため、エージェントルーティングシステムがクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
したがって、何をしたいのか、AIがどのように進むべきかについて非常に明確にする必要があります。たとえば、ウェブ検索を実行させたい場合は、次のように言わないでください:
`一人旅に適した良い国を知っていますか?`
代わりに、次のように尋ねてください:
`ウェブ検索をして、一人旅に最適な国を見つけてください`
---
## 使い方
## **独自のサーバーでLLMを実行するためのセットアップ**
警告: 現在、サポートされている言語は英語、中国語、フランス語のみです。他の言語でのプロンプトは機能しますが、適切なエージェントにルーティングされない場合があります。
サービスが`./start_services.sh`で起動していることを確認し、`python3 cli.py`でagenticSeekを実行します。
```sh
sudo ./start_services.sh
python3 cli.py
```
`>>> `と表示されます
これは、agenticSeekが指示を待っていることを示します。
configで`listen = True`を設定することで、音声認識を使用することもできます。
終了するには、単に`goodbye`と言います。
以下は使用例です:
### コーディング/バッシュ
> *Pythonでスネークゲームを作成*
> *C言語で行列の掛け算を教えて*
> *Golangでブラックジャックを作成*
### ウェブ検索
> *日本の最先端のAI研究を行っているクールなテックスタートアップを見つけるためにウェブ検索を行う*
> *agenticSeekを作成したのは誰かをインターネットで見つけることができますか?*
> *オンラインの燃料計算機を使用して、ニースからミラノまでの旅行の費用を見積もることができますか?*
### ファイルシステム
> *契約書.pdfがどこにあるか見つけてくれませんか?*
> *ディスクにどれだけの空き容量があるか教えて*
> *READMEを読んでプロジェクトを/home/path/projectにインストールしてください*
### カジュアル
> *フランスのレンヌについて教えて*
> *博士号を追求すべきですか?*
> *最高のワークアウトルーチンは何ですか?*
クエリを入力すると、agenticSeekはタスクに最適なエージェントを割り当てます。
これは初期のプロトタイプであるため、エージェントルーティングシステムはクエリに基づいて常に適切なエージェントを割り当てるとは限りません。
したがって、何を望んでいるか、AIがどのように進行するかについて非常に明確にする必要があります。たとえば、ウェブ検索を行いたい場合は、次のように言わないでください:
`一人旅に良い国を知っていますか?`
代わりに、次のように尋ねてください:
`ウェブ検索を行い、一人旅に最適な国を見つけてください`
---
## **ボーナス: 自分のサーバーでLLMを実行するためのセットアップ**
強力なコンピュータやサーバーを持っていて、それをラップトップから使用したい場合、リモートサーバーでLLMを実行するオプションがあります。
強力なコンピューターまたは使用できるサーバーがあるが、ラップトップから使用したい場合は、カスタムLLMサーバーを使用してリモートサーバーでLLMを実行するオプションがあります。
AIモデルを実行する「サーバー」で、IPアドレスを取得します。
```sh
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # ローカルIP
curl https://ipinfo.io/ip # 公開IP
curl https://ipinfo.io/ip # パブリックIP
```
注意: WindowsまたはmacOSの場合、IPアドレスを見つけるには、それぞれ`ipconfig`または`ifconfig`を使用してください
注意WindowsまたはmacOSの場合、それぞれipconfigまたはifconfigを使用してIPアドレスを見つけます
リポジトリをクローンし、`server/`フォルダに入ります。
リポジトリをクローンし、`server/`フォルダーに移動します。
```sh
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
cd agenticSeek/server/
```
サーバー固有の依存関係をインストールします:
サーバー固有の要件をインストールします
```sh
pip3 install -r requirements.txt
```
サーバースクリプトを実行します。
サーバー スクリプトを実行します。
```sh
python3 app.py --provider ollama --port 3333
```
`ollama``llamacpp`のどちらかをLLMサービスとして選択できます。
LLMサービスとして`ollama``llamacpp`のどちらかを選択できます。
次に、個人用コンピュータで以下を行います:
`config.ini`ファイルを変更し、`provider_name``server`に、`provider_model``deepseek-r1:xxb`に設定します。
次に、個人のコンピュータで:
`config.ini`ファイルを変更して、`provider_name``server`に、`provider_model``deepseek-r1:xxb`に設定します。
`provider_server_address`をモデルを実行するマシンのIPアドレスに設定します。
```sh
@@ -321,46 +361,46 @@ provider_model = deepseek-r1:70b
provider_server_address = x.x.x.x:3333
```
次のステップ: [サービスを開始してAgenticSeekを実行する](#Start-services-and-Run)
次のステップ:[サービスの開始とAgenticSeekの実行](#サービスの開始と実行)
---
## 音声認識
現在、音声認識は英語でのみ動作することに注意してください。
現在、音声認識は英語でのみ機能することに注意してください。
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します:
音声認識機能はデフォルトで無効になっています。有効にするには、config.iniファイルでlistenオプションをTrueに設定します
```
listen = True
```
有効にすると、音声認識機能はトリガーキーワード(エージェントの名前)を待ちます。その後、入力を処理します。エージェントの名前は*config.ini*ファイル`agent_name`値を更新することでカスタマイズできます:
有効にすると、音声認識機能は、入力を処理し始める前にトリガーキーワード(エージェントの名前)をリッスンします。*config.ini*ファイル`agent_name`値を更新することで、エージェントの名前をカスタマイズできます
```
agent_name = Friday
```
最適な認識のために、"John"や"Emma"のような一般的な英語の名前をエージェント名として使用することをお勧めします。
最適な認識のためには、エージェント名として「John」や「Emmaのような一般的な英語の名前を使用することをお勧めします。
トランスクリプトが表示され始めたら、エージェントの名前を大声で言って起動します(例:"Friday")。
トランスクリプトが表示され始めたら、エージェントの名前を声に出して起動します(例:Friday)。
クエリを明確に話します。
クエリをはっきりと話します。
リクエストを終了する際に確認フレーズを使用してシステムに進行を通知します。確認フレーズの例は次のようなものがあります:
システムに処理を進めるよう合図するために、確認フレーズでリクエストを終了します。確認フレーズの例は次のとおりです:
```
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
```
## 設定
設定例:
設定例
```
[MAIN]
is_local = True
provider_name = ollama
provider_model = deepseek-r1:1.5b
provider_model = deepseek-r1:32b
provider_server_address = 127.0.0.1:11434
agent_name = Friday
recover_last_session = False
@@ -369,7 +409,7 @@ speak = False
listen = False
work_dir = /Users/mlg/Documents/ai_folder
jarvis_personality = False
languages = en ja
languages = en zh
[BROWSER]
headless_browser = False
stealth_mode = False
@@ -377,112 +417,154 @@ stealth_mode = False
**説明**:
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)。
- provider_name -> 使用するプロバイダー(`ollama``server``lm-studio``deepseek-api`のいずれか)。
- provider_model -> 使用するモデル、例: deepseek-r1:1.5b。
- provider_server_address -> サーバーアドレス、例: 127.0.0.1:11434(ローカルの場合)。非ローカルAPIの場合は何でも設定できます。
- agent_name -> エージェントの名前、例: Friday。TTSのトリガーワードとして使用されます
- recover_last_session -> 最後のセッションから再開する(True)か、しない(False)。
- save_session -> セッションデータを保存する(True)か、しない(False)
- speak -> 音声出力を有効にする(True)か、しない(False)。
- listen -> 音声入力を有効にする(True)か、しない(False)
- work_dir -> AIがアクセスするフォルダー。例: /Users/user/Documents/。
- jarvis_personality -> JARVISのようなパーソナリティを使用する(True)かしない(False。これは単にプロンプトファイルを変更するだけです
- headless_browser -> ウィンドウを表示せずにブラウザを実行する(True)か、しない(False)。
- stealth_mode -> ボット検出を難しくします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです
- languages -> List of supported languages. Required for agent routing system. The longer the languages list the more model will be downloaded.
- is_local -> エージェントをローカルで実行する(True)か、リモートサーバーで実行する(False)
- provider_name -> 使用するプロバイダー(`ollama``server``lm-studio``deepseek-api`のいずれか)
- provider_model -> 使用するモデル、例:deepseek-r1:32b
- provider_server_address -> サーバーアドレス、例:ローカルの場合は127.0.0.1:11434。非ローカルAPIの場合は何でも設定します
- agent_name -> エージェントの名前、例:Friday。TTSのトリガーワードとして使用されます
- recover_last_session -> 前回のセッションから再開する(True)かしない(False
- save_session -> セッションデータを保存する(True)かしない(False)か
- speak -> 音声出力を有効にする(True)かしない(False)か。
- listen -> 音声入力をリッスンする(True)かしない(False)か。
- work_dir -> AIがアクセスできるフォルダ。例:/Users/user/Documents/。
- jarvis_personality -> JARVISのような性格を使用する(True)かしない(False)か。これは単にプロンプトファイルを変更します。
- languages -> サポートされている言語のリスト。LLMルーターが正しく機能するために必要です。あまりにも多くの言語や類似した言語を入れすぎないようにしてください。
- headless_browser -> 表示ウィンドウなしでブラウザを実行する(True)かしない(False)か。
- stealth_mode -> ボット検出を困難にします。唯一の欠点は、anticaptcha拡張機能を手動でインストールする必要があることです。
- languages -> サポートされている言語のリスト。エージェントルーティングシステムに必要です。言語リストが長いほど、ダウンロードされるモデルが多くなります。
## プロバイダー
以下の表は利用可能なプロバイダーを示しています:
以下の表は利用可能なプロバイダーを示しています
| プロバイダー | ローカル? | 説明 |
| プロバイダー | ローカル | 説明 |
|-----------|--------|-----------------------------------------------------------|
| ollama | はい | ollamaをLLMプロバイダーとして使用して、ローカルでLLMを簡単に実行 |
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行 |
| lm-studio | はい | LM studio`lm-studio`を使用してローカルでLLMを実行 |
| ollama | はい | ollamaをLLMプロバイダーとして使用して、LLMをローカルで簡単に実行します |
| server | はい | モデルを別のマシンでホストし、ローカルマシンで実行します |
| lm-studio | はい | LM studioでLLMをローカル実行します`lm-studio` |
| openai | 場合による | ChatGPT API(非プライベート)またはopenai互換APIを使用 |
| deepseek-api | いいえ | Deepseek API(非プライベート) |
| huggingface| いいえ | Hugging-Face API(非プライベート) |
| togetherAI | いいえ | together AI API(非プライベート)を使用
| togetherAI | いいえ | together AI APIを使用(非プライベート) |
| google | いいえ | google gemini APIを使用(非プライベート) |
プロバイダーを選択するには、config.iniを変更します:
プロバイダーを選択するには、config.iniを変更します:
```
is_local = False
provider_name = openai
provider_model = gpt-4o
is_local = True
provider_name = ollama
provider_model = deepseek-r1:32b
provider_server_address = 127.0.0.1:5000
```
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalse。
`is_local`: ローカルで実行されるLLMの場合はTrue、それ以外の場合はFalseである必要があります
`provider_name`: 使用するプロバイダーを名前で選択します。上記のプロバイダーリストを参照してください。
`provider_model`: エージェントが使用するモデルを設定します。
`provider_server_address`: サーバープロバイダーを使用しない場合は何でも設定できます
`provider_server_address`: サーバーアドレス。APIプロバイダーには使用されません
# 既知の問題
## Chromedriverの問題
**既知のエラー#1:** *chromedriverの不一致*
**既知のエラー #1:** *chromedriverの不一致*
`Exception: Failed to initialize browser: Message: session not created: This version of ChromeDriver only supports Chrome version 113
Current browser version is 134.0.6998.89 with binary path`
これは、ブラウザとchromedriverのバージョンが一致しない場合に発生します。
最新バージョンをダウンロードするには、次のリンクにアクセスしてください:
最新バージョンをダウンロードするためにナビゲートする必要があります:
https://developer.chrome.com/docs/chromedriver/downloads
Chromeバージョン115以降を使用している場合は、次のリンクにアクセスしてください:
Chromeバージョン115以降を使用している場合は、以下にアクセスしてください
https://googlechromelabs.github.io/chrome-for-testing/
お使いのOSに対応するchromedriverバージョンをダウンロードします。
そして、OSに一致するchromedriverバージョンをダウンロードします。
![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: どのようなハードウェアが必要ですか?**
| モデルサイズ | GPU | コメント |
|-----------|--------|-----------------------------------------------------------|
| 7B | 8GB VRAM | ⚠️ 推奨されません。パフォーマンスがく、頻繁に幻覚を起こし、プランナーエージェント失敗する可能性が高いです。 |
| 14B | 12GB VRAM (例: RTX 3060) | ✅ 簡単なタスクには使用可能です。ウェブブラウジングや計画タスクには苦労する可能性があります。 |
| 32B | 24GB以上のVRAM (例: RTX 4090) | 🚀 ほとんどのタスクで成功しますが、タスク計画にはまだ苦労する可能性があります。 |
| 70B+ | 48GB以上のVRAM (例: Mac Studio) | 💪 優れた性能。高度なユースケースに推奨されます。 |
|-----------|------------|--------------------------------------------------------------------------|
| 7B | 8GB VRAM | ⚠️ 推奨。パフォーマンスがく、幻覚が頻繁に発生し、プランナーエージェント失敗する可能性が高いです。 |
| 14B | 12GB VRAM(例:RTX 3060 | ✅ 簡単なタスクには使用可能。ウェブブラウジングや計画タスク苦労する可能性があります。 |
| 32B | 24GB以上のVRAM(例:RTX 4090 | 🚀 ほとんどのタスクで成功しますが、タスク計画まだ苦労する可能性があります。 |
| 70B+ | 48GB以上のVRAM(例:mac studio | 💪 素晴らしい。高度なユースケースに推奨されます。 |
**Q: なぜ他のモデルではなくDeepseek R1を選ぶのですか?**
**Q: なぜ他のモデルではなくDeepseek R1のですか?**
Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに最適だと考えています。他のモデルも問題なく動作しますが、Deepseekが私たちの主な選択です。
Deepseek R1は、そのサイズに対して推論とツール使用に優れています。私たちのニーズに合っていると考えており、他のモデルも正常に動作しますが、Deepseekが私たちの主な選択です。
**Q: `cli.py`を実行するとエラーが発生します。どうすればよいですか?**
Ollamaが実行中であることを確認してください`ollama serve`)、`config.ini`がプロバイダー一致していること、および依存関係がインストールされていることを確認してください。それでも解決しない場合は、問題を報告してください。
ローカルが実行されていること`ollama serve`)、`config.ini`がプロバイダー一致していること、依存関係がインストールされていることを確認してください。それでも解決しない場合は、遠慮なく問題を提起してください。
**Q: 本当に100%ローカルで実行できますか?**
はい、OllamaまたはServerプロバイダーを使用すると、すべての音声認識、LLM、および音声合成モデルがローカルで実行されます。非ローカルオプション(OpenAIまたは他のAPI)はオプションです。
はい、Ollama、lm-studio、またはサーバープロバイダーを使用すると、すべての音声認識、LLM、テキスト読み上げモデルがローカルで実行されます。非ローカルオプション(OpenAIまたはその他のAPI)はオプションです。
**Q: Manusを持っているのに、なぜAgenticSeekを使用する必要があるのですか?**
**Q: Manusがあるのに、なぜAgenticSeekを使うべきなのですか?**
これは、AIエージェントに関する興味から始まったサイドプロジェクトです。特別なは、ローカルモデルを使用し、APIを避けことです。
私たちは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) | 台北時間 |(常に忙しい)
+24 -3
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,6 +156,7 @@ async def get_latest_answer():
"uid": uid
}
interaction.current_agent.last_answer = ""
interaction.current_agent.last_reasoning = ""
query_resp_history.append(query_resp)
return JSONResponse(status_code=200, content=query_resp)
if query_resp_history:
@@ -158,6 +170,7 @@ async def think_wrapper(interaction, query):
success = await interaction.think()
if not success:
interaction.last_answer = "Error: No answer from agent"
interaction.last_reasoning = "Error: No reasoning from agent"
interaction.last_success = False
else:
interaction.last_success = True
@@ -166,7 +179,8 @@ async def think_wrapper(interaction, query):
return success
except Exception as e:
logger.error(f"Error in think_wrapper: {str(e)}")
interaction.last_answer = f"Error: {str(e)}"
interaction.last_answer = f""
interaction.last_reasoning = f"Error: {str(e)}"
interaction.last_success = False
raise e
@@ -177,6 +191,7 @@ async def process_query(request: QueryRequest):
query_resp = QueryResponse(
done="false",
answer="",
reasoning="",
agent_name="Unknown",
success="false",
blocks={},
@@ -194,6 +209,7 @@ async def process_query(request: QueryRequest):
if not success:
query_resp.answer = interaction.last_answer
query_resp.reasoning = interaction.last_reasoning
return JSONResponse(status_code=400, content=query_resp.jsonify())
if interaction.current_agent:
@@ -208,11 +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,
@@ -235,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)
+4 -4
View File
@@ -29,7 +29,7 @@ async def main():
is_local=config.getboolean('MAIN', 'is_local'))
browser = Browser(
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode),
create_driver(headless=config.getboolean('BROWSER', 'headless_browser'), stealth_mode=stealth_mode, lang=languages[0]),
anticaptcha_manual_install=stealth_mode
)
@@ -49,9 +49,9 @@ async def main():
PlannerAgent(name="Planner",
prompt_path=f"prompts/{personality_folder}/planner_agent.txt",
provider=provider, verbose=False, browser=browser),
McpAgent(name="MCP Agent",
prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
provider=provider, verbose=False),
#McpAgent(name="MCP Agent",
# prompt_path=f"prompts/{personality_folder}/mcp_agent.txt",
# provider=provider, verbose=False), # NOTE under development
]
interaction = Interaction(agents,
+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:
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;
+61 -10
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'
}`}
>
<div className="message-header">
{msg.type === 'agent' && (
<span className="agent-name">{msg.agentName}</span>
)}
<p>{msg.content}</p>
{msg.type === 'agent' && msg.reasoning && expandedReasoning.has(index) && (
<div className="reasoning-content">
<ReactMarkdown>{msg.reasoning}</ReactMarkdown>
</div>
)}
{msg.type === 'agent' && (
<button
className="reasoning-toggle"
onClick={() => toggleReasoning(index)}
title={expandedReasoning.has(index) ? "Hide reasoning" : "Show reasoning"}
>
{expandedReasoning.has(index) ? '▼' : '▶'} Reasoning
</button>
)}
</div>
<div className="message-content">
<ReactMarkdown>{msg.content}</ReactMarkdown>
</div>
</div>
))
)}
@@ -212,6 +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>
))
) : (
+1 -1
View File
@@ -17,7 +17,6 @@ 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
soundfile>=0.13.1
@@ -41,6 +40,7 @@ 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
+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
-108
View File
@@ -1,108 +0,0 @@
#!/usr/bin python3
"""
self_run.py is a script for automatically creating prompts, and saving history as training data.
"""
import sys
import argparse
import configparser
import asyncio
from sources.llm_provider import Provider
from sources.interaction import Interaction
from sources.agents import Agent, CoderAgent, CasualAgent, FileAgent, PlannerAgent, BrowserAgent, McpAgent
from sources.browser import Browser, create_driver
import warnings
warnings.filterwarnings("ignore")
config = configparser.ConfigParser()
config.read('config.ini')
def copy_conversations_folder():
source_path = "conversations/"
destination_path = "training_data/"
if not os.path.exists(destination_path):
os.makedirs(destination_path)
for filename in os.listdir(source_path):
source_file = os.path.join(source_path, filename)
destination_file = os.path.join(destination_path, filename)
shutil.copy2(source_file, destination_file)
print(f"Copied {source_file} to {destination_file}")
def get_random_query(provider):
prompt = """
You are an expert in crafting queries for AgenticSeek, a AI assistant that autonomously browses the web, writes code, plans tasks, and manages files. It supports tasks like web searches, coding in Python/C/Go/Java, file operations, task planning.
Queries must be explicit, specifying actions like "search the web," "write code," or "save to a file," as AgenticSeek's agent routing may not infer vague intents.
Generate a single realistic user query for AgenticSeek. The query should:
Be concise and explicit about the desired action (e.g., web search, coding, file management).
Align with AgenticSeeks capabilities (web browsing, coding, task planning, file operations).
Include a specific output where relevant (e.g., save to a file with a clear name and path).
Reflect a practical use case (e.g., research, programming, personal tasks).
Be formatted as a single sentence.
Example Query:
Search the web for the best hiking trails in Colorado and save a list of three trails with their locations in hiking_trails.txt in /home/project
"""
history = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": prompt}]
thought = provider.respond(history)
return thought
async def self_runner():
provider = Provider(provider_name=config["MAIN"]["provider_name"],
model=config["MAIN"]["provider_model"],
server_address=config["MAIN"]["provider_server_address"],
is_local=config.getboolean('MAIN', 'is_local'))
browser = Browser(
create_driver(headless=True, stealth_mode=False),
anticaptcha_manual_install=False
)
agents = [
CasualAgent(name=config["MAIN"]["agent_name"],
prompt_path=f"prompts/base/casual_agent.txt",
provider=provider, verbose=False),
CoderAgent(name="coder",
prompt_path=f"prompts/base/coder_agent.txt",
provider=provider, verbose=False),
FileAgent(name="File Agent",
prompt_path=f"prompts/base/file_agent.txt",
provider=provider, verbose=False),
BrowserAgent(name="Browser",
prompt_path=f"prompts/base/browser_agent.txt",
provider=provider, verbose=False, browser=browser),
PlannerAgent(name="Planner",
prompt_path=f"prompts/base/planner_agent.txt",
provider=provider, verbose=False, browser=browser)
]
interaction = Interaction(agents,
tts_enabled=False,
stt_enabled=False,
recover_last_session=False,
langs=['en']
)
print("Start self-running for training data generation...")
try:
while interaction.is_active:
query = get_random_query(provider)
print(f"Generated query: {query}")
interaction.set_query(query)
if await interaction.think():
interaction.show_answer()
except Exception as e:
if config.getboolean('MAIN', 'save_session'):
interaction.save_session()
copy_conversations_folder()
raise e
finally:
if config.getboolean('MAIN', 'save_session'):
interaction.save_session()
copy_conversations_folder()
if __name__ == "__main__":
asyncio.run(self_runner())
+17 -2
View File
@@ -44,7 +44,9 @@ class Agent():
self.blocks_result = []
self.success = True
self.last_answer = ""
self.last_reasoning = ""
self.status_message = "Haven't started yet"
self.stop = False
self.verbose = verbose
self.executor = ThreadPoolExecutor(max_workers=1)
@@ -64,6 +66,10 @@ class Agent():
def get_last_answer(self) -> str:
return self.last_answer
@property
def get_last_reasoning(self) -> str:
return self.last_reasoning
@property
def get_blocks(self) -> list:
return self.blocks_result
@@ -114,6 +120,13 @@ class Agent():
except Exception as e:
raise e
def request_stop(self) -> None:
"""
Request the agent to stop.
"""
self.stop = True
self.status_message = "Stopped"
@abstractmethod
def process(self, prompt, speech_module) -> str:
"""
@@ -127,8 +140,10 @@ class Agent():
Remove the reasoning block of reasoning model like deepseek.
"""
end_tag = "</think>"
end_idx = text.rfind(end_tag)+8
return text[end_idx:]
end_idx = text.rfind(end_tag)
if end_idx == -1:
return text
return text[end_idx+8:]
def extract_reasoning_text(self, text: str) -> None:
"""
+24 -14
View File
@@ -41,7 +41,7 @@ class BrowserAgent(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())
model_provider=provider.get_model_name() if provider else None)
def get_today_date(self) -> str:
"""Get the date"""
@@ -77,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.
"""
@@ -181,6 +181,7 @@ class BrowserAgent(Agent):
animate_thinking("Thinking...", color="status")
self.memory.push('user', prompt)
answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
if show_reasoning:
pretty_print(reasoning, color="failure")
pretty_print(answer, color="output")
@@ -234,19 +235,23 @@ 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, compression = False) -> str:
def get_page_text(self, limit_to_model_ctx = False) -> str:
"""Get the text content of the current page."""
page_text = self.browser.get_text()
if compression:
if limit_to_model_ctx:
#page_text = self.memory.compress_text_to_max_ctx(page_text)
page_text = self.memory.trim_text_to_max_ctx(page_text)
return page_text
@@ -349,11 +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
@@ -365,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.get_page_text()
page_text = self.get_page_text(limit_to_model_ctx=True)
answer = self.handle_update_prompt(user_prompt, page_text, fill_success)
answer, reasoning = await self.llm_decide(prompt)
if Action.FORM_FILLED.value in answer:
pretty_print(f"Filled form. Handling page update.", color="status")
page_text = self.get_page_text()
page_text = self.get_page_text(limit_to_model_ctx=True)
self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text)
continue
@@ -393,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
@@ -407,7 +417,7 @@ class BrowserAgent(Agent):
prompt = self.make_newsearch_prompt(user_prompt, unvisited)
continue
self.current_page = link
page_text = self.get_page_text()
page_text = self.get_page_text(limit_to_model_ctx=True)
self.navigable_links = self.browser.get_navigable()
prompt = self.make_navigation_prompt(user_prompt, page_text)
self.status_message = "Navigating..."
+3 -1
View File
@@ -51,10 +51,12 @@ class CoderAgent(Agent):
self.memory.push('user', prompt)
clarify_trigger = "REQUEST_CLARIFICATION"
while attempt < max_attempts:
while attempt < max_attempts and not self.stop:
print("Stopped?", self.stop)
animate_thinking("Thinking...", color="status")
await self.wait_message(speech_module)
answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
if clarify_trigger in answer:
self.last_answer = answer
await asyncio.sleep(0)
+2 -1
View File
@@ -28,10 +28,11 @@ class FileAgent(Agent):
exec_success = False
prompt += f"\nYou must work in directory: {self.work_dir}"
self.memory.push('user', prompt)
while exec_success is False:
while exec_success is False and not self.stop:
await self.wait_message(speech_module)
animate_thinking("Thinking...", color="status")
answer, reasoning = await self.llm_request()
self.last_reasoning = reasoning
exec_success, _ = self.execute_modules(answer)
answer = self.remove_blocks(answer)
self.last_answer = answer
+1 -2
View File
@@ -35,8 +35,7 @@ class McpAgent(Agent):
"""
api_key_mcp_finder = os.getenv("MCP_FINDER_API_KEY")
if not api_key_mcp_finder or api_key_mcp_finder == "":
pretty_print("MCP Finder API key not found. Please set the MCP_FINDER_API_KEY environment variable.", color="failure")
pretty_print("MCP Finder disabled.", color="failure")
pretty_print("MCP Finder disabled.", color="warning")
self.enabled = False
return {
"mcp_finder": api_key_mcp_finder
+15 -3
View File
@@ -83,11 +83,15 @@ class PlannerAgent(Agent):
self.logger.warning(f"Agent {task['agent']} does not exist.")
pretty_print(f"Agent {task['agent']} does not exist.", color="warning")
return []
try:
agent = {
'agent': task['agent'],
'id': task['id'],
'task': task['task']
}
except:
self.logger.warning("Missing field in json plan.")
return []
self.logger.info(f"Created agent {task['agent']} with task: {task['task']}")
if 'need' in task:
self.logger.info(f"Agent {task['agent']} was given info:\n {task['need']}")
@@ -156,6 +160,7 @@ class PlannerAgent(Agent):
return []
agents_tasks = self.parse_agent_tasks(answer)
if agents_tasks == []:
self.show_plan(agents_tasks, answer)
prompt = f"Failed to parse the tasks. Please write down your task followed by a json plan within ```json. Do not ask for clarification.\n"
pretty_print("Failed to make plan. Retrying...", color="warning")
continue
@@ -178,7 +183,11 @@ class PlannerAgent(Agent):
last_agent_work = agents_work_result[id]
tool_success_str = "success" if success else "failure"
pretty_print(f"Agent {id} work {tool_success_str}.", color="success" if success else "failure")
if int(id) == len(agents_tasks):
try:
id_int = int(id)
except Exception as e:
return agents_tasks
if id_int == len(agents_tasks):
next_task = "No task follow, this was the last step. If it failed add a task to recover."
else:
next_task = f"Next task is: {agents_tasks[int(id)][0]}."
@@ -221,8 +230,9 @@ class PlannerAgent(Agent):
agent_prompt = self.make_prompt(task['task'], required_infos)
pretty_print(f"Agent {task['agent']} started working...", color="status")
self.logger.info(f"Agent {task['agent']} started working on {task['task']}.")
answer, _ = await self.agents[task['agent'].lower()].process(agent_prompt, None)
answer, reasoning = await self.agents[task['agent'].lower()].process(agent_prompt, None)
self.last_answer = answer
self.last_reasoning = reasoning
self.blocks_result = self.agents[task['agent'].lower()].blocks_result
agent_answer = self.agents[task['agent'].lower()].raw_answer_blocks(answer)
success = self.agents[task['agent'].lower()].get_success
@@ -257,7 +267,7 @@ class PlannerAgent(Agent):
return "Failed to parse the tasks.", ""
i = 0
steps = len(agents_tasks)
while i < steps:
while i < steps and not self.stop:
task_name, task = agents_tasks[i][0], agents_tasks[i][1]
self.status_message = "Starting agents..."
pretty_print(f"I will {task_name}.", color="info")
@@ -271,6 +281,8 @@ class PlannerAgent(Agent):
answer, success = await self.start_agent_process(task, required_infos)
except Exception as e:
raise e
if self.stop:
pretty_print(f"Requested stop.", color="failure")
agents_work_result[task['id']] = answer
agents_tasks = await self.update_plan(goal, agents_tasks, agents_work_result, task['id'], success)
steps = len(agents_tasks)
+108 -26
View File
@@ -19,6 +19,7 @@ import time
import random
import os
import shutil
import uuid
import tempfile
import markdownify
import sys
@@ -42,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))
@@ -62,9 +70,9 @@ def get_chrome_path() -> str:
def get_random_user_agent() -> str:
"""Get a random user agent string with associated vendor."""
user_agents = [
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36", "vendor": "Google Inc."},
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15", "vendor": "Apple Inc."},
{"ua": "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0", "vendor": ""},
{"ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Google Inc."},
{"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Apple Inc."},
{"ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36", "vendor": "Google Inc."},
]
return random.choice(user_agents)
@@ -75,6 +83,7 @@ def install_chromedriver() -> str:
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(
@@ -91,7 +100,7 @@ def bypass_ssl() -> str:
"""
This is a fallback for stealth mode to bypass SSL verification. Which can fail on some setup.
"""
pretty_print("This is a workaround for SSL issues but upsafe we strongly advice you update your certifi SSL certificate.", color="warning")
pretty_print("Bypassing SSL verification issues, we strongly advice you update your certifi SSL certificate.", color="warning")
ssl._create_default_https_context = ssl._create_unverified_context
def create_undetected_chromedriver(service, chrome_options) -> webdriver.Chrome:
@@ -107,11 +116,10 @@ def create_undetected_chromedriver(service, chrome_options) -> webdriver.Chrome:
pretty_print(f"Failed to create Chrome driver, fallback failed:\n{str(e)}.", color="failure")
raise e
raise e
# hide webdriver flag
driver.execute_script("Object.defineProperty(navigator, 'webdriver', {get: () => undefined})")
return driver
def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx") -> webdriver.Chrome:
def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx", lang="en") -> webdriver.Chrome:
"""Create a Chrome WebDriver with specified options."""
chrome_options = Options()
chrome_path = get_chrome_path()
@@ -121,24 +129,37 @@ 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:
@@ -154,16 +175,31 @@ def create_driver(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx
stealth(driver,
languages=["en-US", "en"],
vendor=user_agent["vendor"],
platform="Win64" if "Windows" in user_agent["ua"] else "MacIntel" if "Macintosh" in user_agent["ua"] else "Linux x86_64",
platform="Win64" if "windows" in user_agent["ua"].lower() else "MacIntel" if "mac" in user_agent["ua"].lower() else "Linux x86_64",
webgl_vendor="Intel Inc.",
renderer="Intel Iris OpenGL Engine",
fix_hairline=True,
)
return driver
security_prefs = {
"profile.default_content_setting_values.media_stream": 2,
"profile.default_content_setting_values.geolocation": 2,
"profile.default_content_setting_values.geolocation": 0,
"profile.default_content_setting_values.notifications": 0,
"profile.default_content_setting_values.camera": 0,
"profile.default_content_setting_values.microphone": 0,
"profile.default_content_setting_values.midi_sysex": 0,
"profile.default_content_setting_values.clipboard": 0,
"profile.default_content_setting_values.media_stream": 0,
"profile.default_content_setting_values.background_sync": 0,
"profile.default_content_setting_values.sensors": 0,
"profile.default_content_setting_values.accessibility_events": 0,
"safebrowsing.enabled": True,
"credentials_enable_service": False,
"profile.password_manager_enabled": False,
"webkit.webprefs.accelerated_2d_canvas_enabled": True,
"webkit.webprefs.force_dark_mode_enabled": False,
"webkit.webprefs.accelerated_2d_canvas_msaa_sample_count": 4,
"enable_webgl": True,
"enable_webgl2_compute_context": True
}
chrome_options.add_experimental_option("prefs", security_prefs)
chrome_options.add_experimental_option("excludeSwitches", ["enable-automation"])
@@ -184,12 +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
try:
self.driver.get("https://www.google.com")
except Exception as e:
self.logger.log(f"Failed to setup initial tab:" + str(e))
pass
self.screenshot()
def switch_control_tab(self):
@@ -198,14 +239,40 @@ class Browser:
def load_anticatpcha_manually(self):
pretty_print("You might want to install the AntiCaptcha extension for captchas.", color="warning")
try:
self.driver.get(self.anticaptcha)
except Exception as e:
self.logger.log(f"Failed to setup initial tab:" + str(e))
pass
def human_move(element):
actions = ActionChains(driver)
x_offset = random.randint(-5,5)
for _ in range(random.randint(2,5)):
actions.move_by_offset(x_offset, random.randint(-2,2))
actions.pause(random.uniform(0.1,0.3))
actions.click().perform()
def human_scroll(self):
for _ in range(random.randint(1, 3)):
scroll_pixels = random.randint(150, 1200)
self.driver.execute_script(f"window.scrollBy(0, {scroll_pixels});")
time.sleep(random.uniform(0.5, 2.0))
if random.random() < 0.4:
self.driver.execute_script(f"window.scrollBy(0, -{random.randint(50, 300)});")
time.sleep(random.uniform(0.3, 1.0))
def patch_browser_fingerprint(self) -> None:
script = self.load_js("spoofing.js")
self.driver.execute_script(script)
def go_to(self, url:str) -> bool:
"""Navigate to a specified URL."""
time.sleep(random.uniform(0.4, 2.5)) # more human behavior
time.sleep(random.uniform(0.4, 2.5))
try:
initial_handles = self.driver.window_handles
self.driver.get(url)
time.sleep(random.uniform(0.01, 0.3))
try:
wait = WebDriverWait(self.driver, timeout=10)
wait.until(
@@ -217,6 +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:
@@ -616,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):
"""
@@ -639,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/
+2 -1
View File
@@ -22,6 +22,7 @@ class Interaction:
self.current_agent = None
self.last_query = None
self.last_answer = None
self.last_reasoning = None
self.agents = agents
self.tts_enabled = tts_enabled
self.stt_enabled = stt_enabled
@@ -158,7 +159,7 @@ class Interaction:
tmp = self.last_answer
self.current_agent = agent
self.is_generating = True
self.last_answer, _ = await agent.process(self.last_query, self.speech)
self.last_answer, self.last_reasoning = await agent.process(self.last_query, self.speech)
self.is_generating = False
if push_last_agent_memory:
self.current_agent.memory.push('user', self.last_query)
+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']}")
+97 -32
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:
@@ -57,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.
"""
@@ -73,7 +73,8 @@ class Provider:
except AttributeError as e:
raise NotImplementedError(f"{str(e)}\nIs {self.provider_name} implemented ?")
except ModuleNotFoundError as e:
raise ModuleNotFoundError(f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
raise ModuleNotFoundError(
f"{str(e)}\nA import related to provider {self.provider_name} was not found. Is it installed ?")
except Exception as e:
if "try again later" in str(e).lower():
return f"{self.provider_name} server is overloaded. Please try again later."
@@ -106,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.
"""
@@ -141,36 +141,45 @@ class Provider:
pretty_print(f"An error occurred: {str(e)}", color="failure")
break
except KeyError as e:
raise Exception(f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
raise Exception(
f"{str(e)}\nError occured with server route. Are you using the correct address for the config.ini provider?") from e
except Exception as e:
raise e
return thought
def ollama_fn(self, history, verbose = False):
def ollama_fn(self, history, verbose=False):
"""
Use local ollama server to generate text.
Use local or remote Ollama server to generate text.
"""
thought = ""
host = "http://localhost:11434" if self.is_local else f"http://{self.server_address}"
client = OllamaClient(host=host)
try:
stream = chat(
stream = client.chat(
model=self.model,
messages=history,
stream=True,
)
for chunk in stream:
if verbose:
print(chunk['message']['content'], end='', flush=True)
thought += chunk['message']['content']
print(chunk["message"]["content"], end="", flush=True)
thought += chunk["message"]["content"]
except httpx.ConnectError as e:
raise Exception("\nOllama connection failed. provider should not be set to ollama if server address is not localhost") from e
except ollama.ResponseError as e:
if e.status_code == 404:
raise Exception(
f"\nOllama connection failed at {host}. Check if the server is running."
) from e
except Exception as e:
if hasattr(e, 'status_code') and e.status_code == 404:
animate_thinking(f"Downloading {self.model}...")
ollama.pull(self.model)
client.pull(self.model)
self.ollama_fn(history, verbose)
if "refused" in str(e).lower():
raise Exception("Ollama connection failed. is the server running ?") from e
raise Exception(
f"Ollama connection refused at {host}. Is the server running?"
) from e
raise e
return thought
def huggingface_fn(self, history, verbose=False):
@@ -213,6 +222,38 @@ class Provider:
except Exception as e:
raise Exception(f"OpenAI API error: {str(e)}") from e
def anthropic_fn(self, history, verbose=False):
"""
Use Anthropic to generate text.
"""
from anthropic import Anthropic
client = Anthropic(api_key=self.api_key)
system_message = None
messages = []
for message in history:
clean_message = {'role': message['role'], 'content': message['content']}
if message['role'] == 'system':
system_message = message['content']
else:
messages.append(clean_message)
try:
response = client.messages.create(
model=self.model,
max_tokens=1024,
messages=messages,
system=system_message
)
if response is None:
raise Exception("Anthropic response is empty.")
thought = response.content[0].text
if verbose:
print(thought)
return thought
except Exception as e:
raise Exception(f"Anthropic API error: {str(e)}") from e
def google_fn(self, history, verbose=False):
"""
Use google gemini to generate text.
@@ -279,7 +320,7 @@ class Provider:
except Exception as e:
raise Exception(f"Deepseek API error: {str(e)}") from e
def lm_studio_fn(self, history, verbose = False):
def lm_studio_fn(self, history, verbose=False):
"""
Use local lm-studio server to generate text.
lm studio use endpoint /v1/chat/completions not /chat/completions like openai
@@ -304,7 +345,30 @@ class Provider:
raise Exception(f"An error occurred: {str(e)}") from e
return thought
def dsk_deepseek(self, history, verbose = False):
def openrouter_fn(self, history, verbose=False):
"""
Use OpenRouter API to generate text.
"""
client = OpenAI(api_key=self.api_key, base_url="https://openrouter.ai/api/v1")
if self.is_local:
# This case should ideally not be reached if unsafe_providers is set correctly
# and is_local is False in config for openrouter
raise Exception("OpenRouter is not available for local use. Change config.ini")
try:
response = client.chat.completions.create(
model=self.model,
messages=history,
)
if response is None:
raise Exception("OpenRouter response is empty.")
thought = response.choices[0].message.content
if verbose:
print(thought)
return thought
except Exception as e:
raise Exception(f"OpenRouter API error: {str(e)}") from e
def dsk_deepseek(self, history, verbose=False):
"""
Use: xtekky/deepseek4free
For free api. Api key should be set to DSK_DEEPSEEK_API_KEY
@@ -340,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.
"""
@@ -349,6 +413,7 @@ class Provider:
"""
return thought
if __name__ == "__main__":
provider = Provider("server", "deepseek-r1:32b", " x.x.x.x:8080")
res = provider.respond(["user", "Hello, how are you?"])
+3 -1
View File
@@ -17,12 +17,14 @@ class Logger:
def create_logging(self, log_filename):
self.logger = logging.getLogger(log_filename)
self.logger.setLevel(logging.DEBUG)
if not self.logger.handlers:
self.logger.handlers.clear()
self.logger.propagate = False
file_handler = logging.FileHandler(self.log_path)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
self.logger.addHandler(file_handler)
def create_folder(self, path):
"""Create log dir"""
try:
+4 -4
View File
@@ -118,13 +118,13 @@ class Memory():
json_memory = json.load(f)
except FileNotFoundError:
self.logger.warning(f"File not found: {path}")
return None
return {}
except json.JSONDecodeError:
self.logger.warning(f"Error decoding JSON from file: {path}")
return None
return {}
except Exception as e:
self.logger.warning(f"Error loading file {path}: {e}")
return None
return {}
return json_memory
def load_memory(self, agent_type: str = "casual_agent") -> None:
@@ -238,7 +238,7 @@ class Memory():
if len(self.memory[i]['content']) > 1024:
self.memory[i]['content'] = self.summarize(self.memory[i]['content'])
def trip_text_to_max_ctx(self, text: str) -> str:
def trim_text_to_max_ctx(self, text: str) -> str:
"""
Truncate a text to fit within the maximum context size of the model.
"""
+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 -3
View File
@@ -8,7 +8,7 @@ if __name__ == "__main__": # if running as a script for individual testing
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
from sources.tools.tools import Tools
from sources.tools.safety import is_unsafe
from sources.tools.safety import is_any_unsafe
class BashInterpreter(Tools):
"""
@@ -43,9 +43,9 @@ class BashInterpreter(Tools):
for command in commands:
command = f"cd {self.work_dir} && {command}"
command = command.replace('\n', '')
if self.safe_mode and is_unsafe(commands):
if self.safe_mode and is_any_unsafe(commands):
print(f"Unsafe command rejected: {command}")
return "Unsafe command detected, execution aborted."
return "\nUnsafe command: {command}. Execution aborted. This is beyond allowed capabilities report to user."
if self.language_bash_attempt(command) and self.allow_language_exec_bash == False:
continue
try:
@@ -100,6 +100,7 @@ class BashInterpreter(Tools):
r"not permitted",
r"not installed",
r"not found",
r"aborted",
r"no such",
r"too many",
r"too few",
+10 -1
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.
+12 -17
View File
@@ -50,19 +50,15 @@ 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."""
@@ -73,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:
+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
)
+60 -5
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."
@@ -60,15 +83,47 @@ if [ ! -f "docker-compose.yml" ]; then
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 ! $COMPOSE_CMD up; then
# 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
@@ -23,7 +23,7 @@ class TestBrowserAgentParsing(unittest.TestCase):
"https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of",
"www.google.com",
"https://test.org/about?page=1",
"https://weatherstack.com/documentation",
"https://weatherstack.com/documentation"
]
result = self.agent.extract_links(test_text)
self.assertEqual(result, expected)
+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()