Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4a98fa601 | ||
|
|
0eb42e4a42 | ||
|
|
4c0a72ff61 | ||
|
|
021644d786 | ||
|
|
725ceeea20 | ||
|
|
69bbd31abf | ||
|
|
2944ff6892 | ||
|
|
6fd329acc6 | ||
|
|
05aa5c2104 | ||
|
|
a976afdbd7 | ||
|
|
29aafda41d | ||
|
|
37c9652e6d | ||
|
|
f8fc43f6d7 | ||
|
|
3fc2ac5235 | ||
|
|
31205febfe | ||
|
|
1fbd8b135b | ||
|
|
7b8a2271a3 | ||
|
|
4e985d8df6 | ||
|
|
74d38100c4 | ||
|
|
9e4613df71 | ||
|
|
2b8bc83a97 | ||
|
|
872d01f8e5 | ||
|
|
d1ecb15703 | ||
|
|
7cde1549b2 | ||
|
|
1432266694 | ||
|
|
1036c5fbc1 | ||
|
|
77ed25306e | ||
|
|
d47761a6a5 | ||
|
|
e135683426 | ||
|
|
53a47be80c | ||
|
|
75ce967107 | ||
|
|
9a8ce59520 | ||
|
|
a97010739b | ||
|
|
7066f62942 | ||
|
|
fc268ed087 | ||
|
|
781275c881 | ||
|
|
2a69fbef4b | ||
|
|
fa4cef0e74 | ||
|
|
f63b2c436d | ||
|
|
cb6210293a | ||
|
|
165dcb8d97 | ||
|
|
1e827a903b | ||
|
|
16f7f7c917 | ||
|
|
f350378170 | ||
|
|
9611bf4081 | ||
|
|
aeb9145d6e | ||
|
|
889545f356 | ||
|
|
782a933971 | ||
|
|
abc765861a | ||
|
|
8a713e5ec0 | ||
|
|
0decd84541 | ||
|
|
be86fd07e1 | ||
|
|
308ba0e595 | ||
|
|
deaf142a0d |
@@ -4,6 +4,9 @@ WORK_DIR="/Users/username/Documents/workspace_with_my_files"
|
||||
OLLAMA_PORT="11434"
|
||||
LM_STUDIO_PORT="1234"
|
||||
BACKEND_PORT="7777"
|
||||
# Set this to your server's public IP/hostname when accessing the UI from a remote machine.
|
||||
# Example: REACT_APP_BACKEND_URL=http://192.168.1.100:7777
|
||||
REACT_APP_BACKEND_URL=http://localhost:7777
|
||||
CUSTOM_ADDITIONAL_LLM_PORT="11435"
|
||||
OPENAI_API_KEY='xxxxx'
|
||||
DEEPSEEK_API_KEY='xxxxx'
|
||||
@@ -11,3 +14,7 @@ OPENROUTER_API_KEY='xxxxx'
|
||||
TOGETHER_API_KEY='xxxxx'
|
||||
GOOGLE_API_KEY='xxxxx'
|
||||
ANTHROPIC_API_KEY='xxxxx'
|
||||
MINIMAX_API_KEY='xxxxx'
|
||||
# Optional: MiniMax API base URL (default: https://api.minimax.io/v1)
|
||||
# For mainland China users: https://api.minimaxi.com/v1
|
||||
# MINIMAX_BASE_URL='https://api.minimax.io/v1'
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
|
||||
FROM --platform=linux/amd64 python:3.11-slim
|
||||
FROM --platform=linux/amd64 python:3.11.12
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# Install essential packages and Chrome dependencies
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*A **100% local alternative to Manus AI**, this voice-enabled AI assistant autonomously browses the web, writes code, and plans tasks while keeping all data on your device. Tailored for local reasoning models, it runs entirely on your hardware, ensuring complete privacy and zero cloud dependency.*
|
||||
|
||||
@@ -240,6 +240,7 @@ provider_server_address = # Typically ignored or can be left blank when is_local
|
||||
| Hugging Face | `huggingface` | No | Use models from Hugging Face Inference API. | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) |
|
||||
| TogetherAI | `togetherAI` | No | Use various open-source models via TogetherAI API.| [api.together.ai/settings/api-keys](https://api.together.ai/settings/api-keys) |
|
||||
| OpenRouter | `openrouter` | No | Use OpenRouter Models| [https://openrouter.ai/](https://openrouter.ai/) |
|
||||
| MiniMax | `minimax` | No | Use MiniMax models (e.g., MiniMax-M2.7, MiniMax-M2.5).| [platform.minimax.io](https://platform.minimax.io/user-center/basic-information) |
|
||||
|
||||
*Note:*
|
||||
* We advise against using `gpt-4o` or other OpenAI models for complex web browsing and task planning as current prompt optimizations are geared towards models like Deepseek.
|
||||
|
||||
+2
-1
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*一个**100%本地运行的 Manus AI 替代品**,支持语音的 AI 助手,可自主浏览网页、编写代码、规划任务,所有数据仅保存在你的设备上。专为本地推理模型设计,完全在你的硬件上运行,确保隐私无忧,无需云端依赖。*
|
||||
|
||||
@@ -230,6 +230,7 @@ provider_server_address = # 当 is_local = False 时,对于大多数 API 通
|
||||
| Hugging Face | `huggingface` | 否 | 使用 Hugging Face Inference API 中的模型。 | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) |
|
||||
| TogetherAI | `togetherAI` | 否 | 通过 TogetherAI API 使用各种开源模型。| [api.together.ai/settings/api-keys](https://api.together.ai/settings/api-keys) |
|
||||
| OpenRouter | `openrouter` | No | 通过 OpenRouter 使用各种开源模型| [https://openrouter.ai/](https://openrouter.ai/) |
|
||||
| MiniMax | `minimax` | 否 | 使用 MiniMax 模型(如 MiniMax-M2.7、MiniMax-M2.5)。 | [platform.minimax.io](https://platform.minimax.io/user-center/basic-information) |
|
||||
|
||||
*注意:*
|
||||
* 我们不建议将 `gpt-4o` 或其他 OpenAI 模型用于复杂的网页浏览和任务规划,因为当前的提示优化针对 Deepseek 等模型。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*一個**100%本地運行的 Manus AI 替代品**,支援語音的 AI 助手,可自主瀏覽網頁、編寫代碼、規劃任務,所有數據僅保存在你的設備上。專為本地推理模型設計,完全在你的硬件上運行,確保隱私無憂,無需雲端依賴。*
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*Un asistente de IA con capacidad de voz que es una **alternativa 100% local a Manus AI**, navega autónomamente por la web, escribe código y planifica tareas manteniendo todos los datos en tu dispositivo. Diseñado para modelos de razonamiento local, funciona completamente en tu hardware, garantizando privacidad total y cero dependencia de la nube.*
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*Un assistant IA avec reconnaissance vocale qui est une **alternative 100% locale à Manus AI**, navigue de manière autonome sur le web, écrit du code et planifie des tâches tout en gardant toutes les données sur votre appareil. Conçu pour des modèles de raisonnement locaux, il fonctionne entièrement sur votre matériel, garantissant une confidentialité totale et zéro dépendance au cloud.*
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*音声対応のAIアシスタントで、**100%ローカルで動作するManus AIの代替品**です。自律的にウェブを閲覧し、コードを書き、タスクを計画し、すべてのデータをデバイス上に保持します。ローカル推論モデル向けに設計されており、完全にあなたのハードウェア上で動作し、プライバシーを保証し、クラウドへの依存をゼロにします。*
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md)
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*Um assistente de IA com reconhecimento de voz que é uma **alternativa 100% local ao Manus AI**, navega autonomamente na web, escreve código e planeja tarefas enquanto mantém todos os dados no seu dispositivo. Projetado para modelos de raciocínio local, funciona inteiramente no seu hardware, garantindo total privacidade e zero dependência de nuvem.*
|
||||
|
||||
|
||||
+692
@@ -0,0 +1,692 @@
|
||||
# AgenticSeek: Gizlilik Odaklı, Yerel Manus Alternatifi
|
||||
|
||||
<p align="center">
|
||||
<img align="center" src="./media/agentic_seek_logo.png" width="300" height="300" alt="Agentic Seek Logo">
|
||||
<p>
|
||||
|
||||
English | [中文](./README_CHS.md) | [繁體中文](./README_CHT.md) | [Français](./README_FR.md) | [日本語](./README_JP.md) | [Português (Brasil)](./README_PTBR.md) | [Español](./README_ES.md) | [Türkçe](./README_TR.md)
|
||||
|
||||
*Sesli komut destekli bu yapay zeka asistanı, **Manus AI'ya %100 yerel bir alternatiftir**. Web'de otonom olarak gezinir, kod yazar ve görevleri planlar; tüm verilerinizi cihazınızda tutar. Yerel akıl yürütme modelleri için tasarlanmış olup tamamen kendi donanımınızda çalışır, eksiksiz gizlilik ve sıfır bulut bağımlılığı sağlar.*
|
||||
|
||||
[](https://fosowl.github.io/agenticSeek.html)  [](https://discord.gg/8hGDaME3TC) [](https://x.com/Martin993886460) [](https://github.com/Fosowl/agenticSeek/stargazers)
|
||||
|
||||
### Neden AgenticSeek?
|
||||
|
||||
* 🔒 Tamamen Yerel ve Gizli - Her şey kendi makinenizde çalışır — bulut yok, veri paylaşımı yok. Dosyalarınız, konuşmalarınız ve aramalarınız gizli kalır.
|
||||
|
||||
* 🌐 Akıllı Web Tarama - AgenticSeek internette kendi başına gezinebilir — arama yapar, okur, bilgi çıkarır, web formlarını doldurur — tamamen eller serbest.
|
||||
|
||||
* 💻 Otonom Kodlama Asistanı - Koda mı ihtiyacınız var? Python, C, Go, Java ve daha fazlasında program yazabilir, hata ayıklayabilir ve çalıştırabilir — denetim olmadan.
|
||||
|
||||
* 🧠 Akıllı Ajan Seçimi - Siz sorarsınız, o otomatik olarak iş için en uygun ajanı belirler. Elinizin altında uzmanlardan oluşan bir ekip gibi.
|
||||
|
||||
* 📋 Karmaşık Görevleri Planlar ve Yürütür - Seyahat planlamasından karmaşık projelere kadar — büyük görevleri adımlara bölebilir ve birden fazla yapay zeka ajanı kullanarak işleri tamamlayabilir.
|
||||
|
||||
* 🎙️ Ses Desteği - Temiz, hızlı, fütüristik ses ve konuşmadan metne dönüştürme ile bilim kurgu filmlerindeki kişisel yapay zekanızla konuşuyormuş gibi etkileşim kurabilirsiniz. (Geliştirme aşamasında)
|
||||
|
||||
### **Demo**
|
||||
|
||||
> *AgenticSeek projesini arayabilir misin, hangi becerilerin gerektiğini öğrenip ardından CV_candidates.zip dosyasını açarak projeye en uygun adayları söyleyebilir misin?*
|
||||
|
||||
https://github.com/user-attachments/assets/b8ca60e9-7b3b-4533-840e-08f9ac426316
|
||||
|
||||
Uyarı: Bu demo ve görünen tüm dosyalar (ör: CV_candidates.zip) tamamen kurgusaldır. Bir şirket değiliz, aday değil açık kaynak katkıda bulunanlar arıyoruz.
|
||||
|
||||
> 🛠⚠️️ **Aktif Olarak Geliştirilmektedir**
|
||||
|
||||
> 🙏 Bu proje bir yan proje olarak başladı ve sıfır yol haritası, sıfır finansmanla geliştirilmektedir. GitHub Trending'e girerek beklentilerin çok ötesine geçti. Katkılar, geri bildirimler ve sabır derinden takdir edilmektedir.
|
||||
|
||||
## Ön Gereksinimler
|
||||
|
||||
Başlamadan önce aşağıdaki yazılımların yüklü olduğundan emin olun:
|
||||
|
||||
* **Git:** Depoyu klonlamak için. [Git İndir](https://git-scm.com/downloads)
|
||||
* **Python 3.10.x:** Python 3.10.x sürümünü kullanmanızı şiddetle tavsiye ederiz. Diğer sürümler bağımlılık hatalarına yol açabilir. [Python 3.10 İndir](https://www.python.org/downloads/release/python-3100/) (3.10.x sürümünü seçin).
|
||||
* **Docker Engine ve Docker Compose:** SearxNG gibi paketlenmiş servisleri çalıştırmak için.
|
||||
* Docker Desktop yükleyin (Docker Compose V2 dahildir): [Windows](https://docs.docker.com/desktop/install/windows-install/) | [Mac](https://docs.docker.com/desktop/install/mac-install/) | [Linux](https://docs.docker.com/desktop/install/linux-install/)
|
||||
* Alternatif olarak, Linux üzerinde Docker Engine ve Docker Compose'u ayrı ayrı yükleyin: [Docker Engine](https://docs.docker.com/engine/install/) | [Docker Compose](https://docs.docker.com/compose/install/) (Compose V2 yüklediğinizden emin olun, ör: `sudo apt-get install docker-compose-plugin`).
|
||||
|
||||
### 1. **Depoyu klonlayın ve kurulumu yapın**
|
||||
|
||||
```sh
|
||||
git clone https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
### 2. .env dosyasının içeriğini değiştirin
|
||||
|
||||
```sh
|
||||
SEARXNG_BASE_URL="http://searxng:8080" # CLI modunda çalıştırıyorsanız http://127.0.0.1:8080 kullanın
|
||||
REDIS_BASE_URL="redis://redis:6379/0"
|
||||
WORK_DIR="/Users/mlg/Documents/workspace_for_ai"
|
||||
OLLAMA_PORT="11434"
|
||||
LM_STUDIO_PORT="1234"
|
||||
CUSTOM_ADDITIONAL_LLM_PORT="11435"
|
||||
OPENAI_API_KEY='optional'
|
||||
DEEPSEEK_API_KEY='optional'
|
||||
OPENROUTER_API_KEY='optional'
|
||||
TOGETHER_API_KEY='optional'
|
||||
GOOGLE_API_KEY='optional'
|
||||
ANTHROPIC_API_KEY='optional'
|
||||
```
|
||||
|
||||
`.env` dosyasını ihtiyaçlarınıza göre güncelleyin:
|
||||
|
||||
- **SEARXNG_BASE_URL**: CLI modunda çalışmıyorsanız değiştirmeyin.
|
||||
- **REDIS_BASE_URL**: Değiştirmeyin.
|
||||
- **WORK_DIR**: Yerel makinenizdeki çalışma dizininin yolu. AgenticSeek bu dosyaları okuyabilir ve onlarla etkileşim kurabilir.
|
||||
- **OLLAMA_PORT**: Ollama servisi için port numarası.
|
||||
- **LM_STUDIO_PORT**: LM Studio servisi için port numarası.
|
||||
- **CUSTOM_ADDITIONAL_LLM_PORT**: Ek özel LLM servisi için port.
|
||||
|
||||
**API anahtarları, LLM'yi yerel olarak çalıştırmayı tercih eden kullanıcılar için tamamen isteğe bağlıdır. Bu projenin birincil amacı da budur. Yeterli donanımınız varsa boş bırakın.**
|
||||
|
||||
### 3. **Docker'ı Başlatın**
|
||||
|
||||
Docker'ın sisteminizde yüklü ve çalışır durumda olduğundan emin olun. Docker'ı aşağıdaki komutlarla başlatabilirsiniz:
|
||||
|
||||
- **Linux/macOS:**
|
||||
Terminal açın ve çalıştırın:
|
||||
```sh
|
||||
sudo systemctl start docker
|
||||
```
|
||||
Veya yüklüyse Docker Desktop'ı uygulama menüsünden başlatın.
|
||||
|
||||
- **Windows:**
|
||||
Başlat menüsünden Docker Desktop'ı başlatın.
|
||||
|
||||
Docker'ın çalıştığını doğrulamak için:
|
||||
```sh
|
||||
docker info
|
||||
```
|
||||
Docker kurulumunuz hakkında bilgi görüyorsanız, düzgün çalışıyor demektir.
|
||||
|
||||
Özet için aşağıdaki [Yerel Sağlayıcılar](#yerel-sağlayıcılar-listesi) tablosuna bakın.
|
||||
|
||||
Sonraki adım: [AgenticSeek'i yerel olarak çalıştırın](#servisleri-başlatın-ve-çalıştırın)
|
||||
|
||||
*Sorun yaşıyorsanız [Sorun Giderme](#sorun-giderme) bölümüne bakın.*
|
||||
*Donanımınız LLM'leri yerel olarak çalıştıramıyorsa [API ile Çalıştırma Kurulumu](#api-ile-çalıştırma-kurulumu) bölümüne bakın.*
|
||||
*Ayrıntılı `config.ini` açıklamaları için [Yapılandırma](#yapılandırma) bölümüne bakın.*
|
||||
|
||||
---
|
||||
|
||||
## LLM'yi Makinenizde Yerel Olarak Çalıştırma Kurulumu
|
||||
|
||||
**Donanım Gereksinimleri:**
|
||||
|
||||
LLM'leri yerel olarak çalıştırmak için yeterli donanıma ihtiyacınız olacaktır. En azından Magistral, Qwen veya Deepseek 14B çalıştırabilecek bir GPU gereklidir. Ayrıntılı model/performans önerileri için SSS bölümüne bakın.
|
||||
|
||||
**Yerel sağlayıcınızı ayarlayın**
|
||||
|
||||
Yerel sağlayıcınızı başlatın (örneğin ollama ile):
|
||||
|
||||
AgenticSeek'i ana makinede (CLI modu) çalıştırmayacaksanız, sağlayıcı dinleme adresini ayarlayın:
|
||||
|
||||
```sh
|
||||
export OLLAMA_HOST=0.0.0.0:11434
|
||||
```
|
||||
|
||||
Ardından sağlayıcınızı başlatın:
|
||||
|
||||
```sh
|
||||
ollama serve
|
||||
```
|
||||
|
||||
Desteklenen yerel sağlayıcıların listesi için aşağıya bakın.
|
||||
|
||||
**config.ini dosyasını güncelleyin**
|
||||
|
||||
config.ini dosyasında provider_name'i desteklenen bir sağlayıcıya ve provider_model'i sağlayıcınızın desteklediği bir LLM'e ayarlayın. *Magistral* veya *Deepseek* gibi akıl yürütme modellerini öneriyoruz.
|
||||
|
||||
Gerekli donanım için README sonundaki **SSS** bölümüne bakın.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = True # Yerel mi yoksa uzak sağlayıcı ile mi çalıştırıyorsunuz.
|
||||
provider_name = ollama # veya lm-studio, openai, vb.
|
||||
provider_model = deepseek-r1:14b # donanımınıza uygun bir model seçin
|
||||
provider_server_address = 127.0.0.1:11434
|
||||
agent_name = Jarvis # yapay zekanızın adı
|
||||
recover_last_session = True # önceki oturumu kurtarıp kurtarmayacağı
|
||||
save_session = True # mevcut oturumu hatırlayıp hatırlamayacağı
|
||||
speak = False # metinden sese dönüştürme
|
||||
listen = False # sesten metne dönüştürme, yalnızca CLI için, deneysel
|
||||
jarvis_personality = False # daha "Jarvis" benzeri bir kişilik kullanıp kullanmayacağı (deneysel)
|
||||
languages = en zh # Dil listesi, metinden sese varsayılan olarak listedeki ilk dili kullanır
|
||||
[BROWSER]
|
||||
headless_browser = True # CLI modunda değilseniz değiştirmeyin.
|
||||
stealth_mode = True # Tarayıcı algılamasını azaltmak için gizli selenium kullanır
|
||||
```
|
||||
|
||||
**Uyarı**:
|
||||
|
||||
- `config.ini` dosya biçimi yorum satırlarını desteklemez.
|
||||
Örnek yapılandırmayı doğrudan kopyalayıp yapıştırmayın, çünkü yorum satırları hatalara neden olur. Bunun yerine `config.ini` dosyasını yorum satırları olmadan istediğiniz ayarlarla manuel olarak düzenleyin.
|
||||
|
||||
- LLM çalıştırmak için LM-studio kullanıyorsanız provider_name'i `openai` olarak *AYARLAMAYIN*. `lm-studio` olarak ayarlayın.
|
||||
|
||||
- Bazı sağlayıcılar (ör: lm-studio) IP'nin önünde `http://` gerektirir. Örneğin: `http://127.0.0.1:1234`
|
||||
|
||||
**Yerel Sağlayıcılar Listesi**
|
||||
|
||||
| Sağlayıcı | Yerel mi? | Açıklama |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| ollama | Evet | Ollama kullanarak LLM'leri kolayca yerel olarak çalıştırın |
|
||||
| lm-studio | Evet | LM Studio ile LLM'leri yerel olarak çalıştırın (`provider_name` değerini `lm-studio` olarak ayarlayın)|
|
||||
| openai | Evet | OpenAI uyumlu API kullanın (ör: llama.cpp sunucusu) |
|
||||
|
||||
Sonraki adım: [Servisleri başlatın ve AgenticSeek'i çalıştırın](#servisleri-başlatın-ve-çalıştırın)
|
||||
|
||||
*Sorun yaşıyorsanız [Sorun Giderme](#sorun-giderme) bölümüne bakın.*
|
||||
*Donanımınız LLM'leri yerel olarak çalıştıramıyorsa [API ile Çalıştırma Kurulumu](#api-ile-çalıştırma-kurulumu) bölümüne bakın.*
|
||||
*Ayrıntılı `config.ini` açıklamaları için [Yapılandırma](#yapılandırma) bölümüne bakın.*
|
||||
|
||||
## API ile Çalıştırma Kurulumu
|
||||
|
||||
Bu kurulum harici, bulut tabanlı LLM sağlayıcılarını kullanır. Seçtiğiniz servisten bir API anahtarına ihtiyacınız olacaktır.
|
||||
|
||||
**1. Bir API Sağlayıcısı Seçin ve API Anahtarı Alın:**
|
||||
|
||||
Aşağıdaki [API Sağlayıcıları Listesi](#api-sağlayıcıları-listesi) bölümüne bakın. Kaydolmak ve API anahtarı almak için web sitelerini ziyaret edin.
|
||||
|
||||
**2. API Anahtarınızı Ortam Değişkeni Olarak Ayarlayın:**
|
||||
|
||||
* **Linux/macOS:**
|
||||
Terminal açın ve `export` komutunu kullanın. Kalıcılık için bunu shell profil dosyanıza (ör: `~/.bashrc`, `~/.zshrc`) eklemeniz önerilir.
|
||||
```sh
|
||||
export PROVIDER_API_KEY="api_anahtarınız"
|
||||
# PROVIDER_API_KEY'i ilgili değişken adıyla değiştirin, ör: OPENAI_API_KEY, GOOGLE_API_KEY
|
||||
```
|
||||
TogetherAI örneği:
|
||||
```sh
|
||||
export TOGETHER_API_KEY="xxxxxxxxxxxxxxxxxxxxxx"
|
||||
```
|
||||
* **Windows:**
|
||||
* **Komut İstemi (Geçerli oturum için geçici):**
|
||||
```cmd
|
||||
set PROVIDER_API_KEY=api_anahtarınız
|
||||
```
|
||||
* **PowerShell (Geçerli oturum için geçici):**
|
||||
```powershell
|
||||
$env:PROVIDER_API_KEY="api_anahtarınız"
|
||||
```
|
||||
* **Kalıcı:** Windows arama çubuğunda "ortam değişkenleri" arayın, "Sistem ortam değişkenlerini düzenle" seçeneğine tıklayın, ardından "Ortam Değişkenleri..." düğmesine tıklayın. Uygun adla (ör: `OPENAI_API_KEY`) yeni bir Kullanıcı değişkeni ekleyin ve değer olarak anahtarınızı girin.
|
||||
|
||||
*(Daha fazla ayrıntı için SSS: [API anahtarlarını nasıl ayarlarım?](#api-anahtarlarını-nasıl-ayarlarım) bölümüne bakın.)*
|
||||
|
||||
|
||||
**3. `config.ini` Dosyasını Güncelleyin:**
|
||||
```ini
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = openai # Veya google, deepseek, togetherAI, huggingface
|
||||
provider_model = gpt-3.5-turbo # Veya gemini-1.5-flash, deepseek-chat, mistralai/Mixtral-8x7B-Instruct-v0.1 vb.
|
||||
provider_server_address = # is_local = False olduğunda çoğu API için genellikle yok sayılır veya boş bırakılabilir
|
||||
# ... diğer ayarlar ...
|
||||
```
|
||||
*Uyarı:* config.ini değerlerinin sonunda boşluk olmadığından emin olun.
|
||||
|
||||
**API Sağlayıcıları Listesi**
|
||||
|
||||
| Sağlayıcı | `provider_name` | Yerel mi? | Açıklama | API Anahtarı Bağlantısı (Örnekler) |
|
||||
|--------------|-----------------|--------|---------------------------------------------------|---------------------------------------------|
|
||||
| OpenAI | `openai` | Hayır | OpenAI API'si üzerinden ChatGPT modellerini kullanın. | [platform.openai.com/signup](https://platform.openai.com/signup) |
|
||||
| Google Gemini| `google` | Hayır | Google AI Studio üzerinden Google Gemini modellerini kullanın. | [aistudio.google.com/keys](https://aistudio.google.com/keys) |
|
||||
| Deepseek | `deepseek` | Hayır | Deepseek API'si üzerinden Deepseek modellerini kullanın. | [platform.deepseek.com](https://platform.deepseek.com) |
|
||||
| Hugging Face | `huggingface` | Hayır | Hugging Face Inference API'den modelleri kullanın. | [huggingface.co/settings/tokens](https://huggingface.co/settings/tokens) |
|
||||
| TogetherAI | `togetherAI` | Hayır | TogetherAI API'si üzerinden çeşitli açık kaynak modelleri kullanın.| [api.together.ai/settings/api-keys](https://api.together.ai/settings/api-keys) |
|
||||
| OpenRouter | `openrouter` | Hayır | OpenRouter Modellerini kullanın| [https://openrouter.ai/](https://openrouter.ai/) |
|
||||
|
||||
*Not:*
|
||||
* Karmaşık web tarama ve görev planlama için `gpt-4o` veya diğer OpenAI modellerini kullanmanızı önermiyoruz, çünkü mevcut prompt optimizasyonları Deepseek gibi modellere yöneliktir.
|
||||
* Kodlama/bash görevleri Gemini ile sorun yaşayabilir, çünkü Deepseek için optimize edilmiş biçimlendirme talimatlarına kesinlikle uymayabilir.
|
||||
* `is_local = False` olduğunda `config.ini` içindeki `provider_server_address` genellikle kullanılmaz, çünkü API uç noktaları genellikle ilgili sağlayıcı kütüphanesinde sabit kodlanmıştır.
|
||||
|
||||
Sonraki adım: [Servisleri başlatın ve AgenticSeek'i çalıştırın](#servisleri-başlatın-ve-çalıştırın)
|
||||
|
||||
*Sorun yaşıyorsanız **Bilinen Sorunlar** bölümüne bakın*
|
||||
|
||||
*Ayrıntılı yapılandırma dosyası açıklamaları için **Yapılandırma** bölümüne bakın.*
|
||||
|
||||
---
|
||||
|
||||
## Servisleri Başlatın ve Çalıştırın
|
||||
|
||||
Varsayılan olarak AgenticSeek tamamen Docker içinde çalıştırılır.
|
||||
|
||||
**Seçenek 1:** Docker'da çalıştırın, web arayüzünü kullanın:
|
||||
|
||||
Gerekli servisleri başlatın. Bu, docker-compose.yml'deki tüm servisleri başlatacaktır:
|
||||
- searxng
|
||||
- redis (searxng için gerekli)
|
||||
- frontend
|
||||
- backend (web arayüzü kullanırken `full` parametresi ile)
|
||||
|
||||
```sh
|
||||
./start_services.sh full # MacOS
|
||||
start start_services.cmd full # Windows
|
||||
```
|
||||
|
||||
**Uyarı:** Bu adım tüm Docker imajlarını indirip yükleyecektir, bu işlem 30 dakikaya kadar sürebilir. Servisleri başlattıktan sonra, herhangi bir mesaj göndermeden önce backend servisinin tamamen çalışır duruma gelmesini bekleyin (loglarda **backend: "GET /health HTTP/1.1" 200 OK** mesajını görmelisiniz). İlk çalıştırmada backend servisinin başlaması 5 dakika sürebilir.
|
||||
|
||||
`http://localhost:3000/` adresine gidin ve web arayüzünü görmelisiniz.
|
||||
|
||||
*Servis başlatma sorun giderme:* Bu betikler başarısız olursa, Docker Engine'in çalıştığından ve Docker Compose'un (V2, `docker compose`) doğru şekilde yüklendiğinden emin olun. Hata mesajları için terminal çıktısını kontrol edin. Bkz. [SSS: AgenticSeek'i veya betiklerini çalıştırırken hata alıyorum.](#sss-sorun-giderme)
|
||||
|
||||
**Seçenek 2:** CLI modu:
|
||||
|
||||
CLI arayüzü ile çalıştırmak için paketleri ana makineye yüklemeniz gerekir:
|
||||
|
||||
```sh
|
||||
./install.sh
|
||||
./install.bat # windows
|
||||
```
|
||||
|
||||
Ardından `config.ini` içindeki SEARXNG_BASE_URL değerini şu şekilde değiştirmelisiniz:
|
||||
|
||||
```sh
|
||||
SEARXNG_BASE_URL="http://localhost:8080"
|
||||
```
|
||||
|
||||
Gerekli servisleri başlatın. Bu, docker-compose.yml'deki bazı servisleri başlatacaktır:
|
||||
- searxng
|
||||
- redis (searxng için gerekli)
|
||||
- frontend
|
||||
|
||||
```sh
|
||||
./start_services.sh # MacOS
|
||||
start start_services.cmd # Windows
|
||||
```
|
||||
|
||||
Çalıştırın: `uv run python -m ensurepip` komutu ile uv'nin pip'i etkin olduğundan emin olun.
|
||||
|
||||
CLI'yi kullanın: `uv run cli.py`
|
||||
|
||||
---
|
||||
|
||||
## Kullanım
|
||||
|
||||
Servislerin `./start_services.sh full` ile çalıştığından emin olun ve web arayüzü için `localhost:3000` adresine gidin.
|
||||
|
||||
Yapılandırmada `listen = True` ayarlayarak konuşmadan metne dönüştürmeyi de kullanabilirsiniz. Yalnızca CLI modu için.
|
||||
|
||||
Çıkmak için `goodbye` yazın/söyleyin.
|
||||
|
||||
İşte bazı kullanım örnekleri:
|
||||
|
||||
> *Python'da bir yılan oyunu yap!*
|
||||
|
||||
> *Rennes, Fransa'daki en iyi kafeleri internette ara ve adres bilgileriyle birlikte üçünü rennes_cafes.txt dosyasına kaydet.*
|
||||
|
||||
> *Bir sayının faktöriyelini hesaplayan bir Go programı yaz, çalışma alanına factorial.go olarak kaydet*
|
||||
|
||||
> *summer_pictures klasöründeki tüm JPG dosyalarını ara, bugünün tarihiyle yeniden adlandır ve yeniden adlandırılan dosyaların listesini photos_list.txt dosyasına kaydet*
|
||||
|
||||
> *2024'ün popüler bilim kurgu filmlerini internette ara ve bu gece izlemek için üçünü seç. Listeyi movie_night.txt dosyasına kaydet.*
|
||||
|
||||
> *2025'in en son yapay zeka haberleri makalelerini internette ara, üçünü seç ve başlıklarını ve özetlerini kazımak için bir Python betiği yaz. Betiği news_scraper.py olarak ve özetleri /home/projects içinde ai_news.txt olarak kaydet*
|
||||
|
||||
> *Cuma, internette ücretsiz bir hisse senedi fiyat API'si ara, supersuper7434567@gmail.com ile kaydol ve ardından API'yi kullanarak Tesla'nın günlük fiyatlarını çekmek için bir Python betiği yaz, sonuçları stock_prices.csv dosyasına kaydet*
|
||||
|
||||
*Form doldurma yeteneklerinin hâlâ deneysel olduğunu ve başarısız olabileceğini unutmayın.*
|
||||
|
||||
Sorgunuzu yazdıktan sonra AgenticSeek göreve en uygun ajanı atayacaktır.
|
||||
|
||||
Bu erken bir prototip olduğundan, ajan yönlendirme sistemi sorgunuza göre her zaman doğru ajanı atamayabilir.
|
||||
|
||||
Bu nedenle, ne istediğiniz ve yapay zekanın nasıl ilerlemesi gerektiği konusunda çok açık olun. Örneğin, web araması yapmasını istiyorsanız şunu demeyin:
|
||||
|
||||
`Solo seyahat için iyi ülkeler biliyor musun?`
|
||||
|
||||
Bunun yerine şöyle sorun:
|
||||
|
||||
`Web araması yap ve solo seyahat için en iyi ülkeleri bul`
|
||||
|
||||
---
|
||||
|
||||
## **LLM'yi Kendi Sunucunuzda Çalıştırma Kurulumu**
|
||||
|
||||
Güçlü bir bilgisayarınız veya kullanabileceğiniz bir sunucunuz varsa, ancak dizüstü bilgisayarınızdan kullanmak istiyorsanız, özel LLM sunucumuzu kullanarak LLM'yi uzak bir sunucuda çalıştırma seçeneğiniz vardır.
|
||||
|
||||
Yapay zeka modelini çalıştıracak "sunucunuzda" IP adresini alın
|
||||
|
||||
```sh
|
||||
ip a | grep "inet " | grep -v 127.0.0.1 | awk '{print $2}' | cut -d/ -f1 # yerel IP
|
||||
curl https://ipinfo.io/ip # genel IP
|
||||
```
|
||||
|
||||
Not: Windows veya macOS için IP adresini bulmak üzere sırasıyla ipconfig veya ifconfig kullanın.
|
||||
|
||||
Depoyu klonlayın ve `server/` klasörüne girin.
|
||||
|
||||
```sh
|
||||
git clone --depth 1 https://github.com/Fosowl/agenticSeek.git
|
||||
cd agenticSeek/llm_server/
|
||||
```
|
||||
|
||||
Sunucuya özel gereksinimleri yükleyin:
|
||||
|
||||
```sh
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
Sunucu betiğini çalıştırın.
|
||||
|
||||
```sh
|
||||
python3 app.py --provider ollama --port 3333
|
||||
```
|
||||
|
||||
LLM servisi olarak `ollama` veya `llamacpp` arasında seçim yapabilirsiniz.
|
||||
|
||||
Şimdi kişisel bilgisayarınızda:
|
||||
|
||||
`config.ini` dosyasında `provider_name` değerini `server` ve `provider_model` değerini `deepseek-r1:xxb` olarak ayarlayın.
|
||||
`provider_server_address` değerini modeli çalıştıracak makinenin IP adresine ayarlayın.
|
||||
|
||||
```sh
|
||||
[MAIN]
|
||||
is_local = False
|
||||
provider_name = server
|
||||
provider_model = deepseek-r1:70b
|
||||
provider_server_address = http://x.x.x.x:3333
|
||||
```
|
||||
|
||||
Sonraki adım: [Servisleri başlatın ve AgenticSeek'i çalıştırın](#servisleri-başlatın-ve-çalıştırın)
|
||||
|
||||
---
|
||||
|
||||
## Konuşmadan Metne
|
||||
|
||||
Uyarı: Konuşmadan metne özelliği şu anda yalnızca CLI modunda çalışmaktadır.
|
||||
|
||||
Konuşmadan metne özelliğinin şu anda yalnızca İngilizce dilinde çalıştığını lütfen unutmayın.
|
||||
|
||||
Konuşmadan metne işlevi varsayılan olarak devre dışıdır. Etkinleştirmek için config.ini dosyasında listen seçeneğini True olarak ayarlayın:
|
||||
|
||||
```
|
||||
listen = True
|
||||
```
|
||||
|
||||
Etkinleştirildiğinde, konuşmadan metne özelliği girdinizi işlemeye başlamadan önce bir tetikleyici anahtar kelimeyi (ajanın adı) dinler. Ajanın adını *config.ini* dosyasındaki `agent_name` değerini güncelleyerek özelleştirebilirsiniz:
|
||||
|
||||
```
|
||||
agent_name = Friday
|
||||
```
|
||||
|
||||
En iyi tanıma performansı için ajan adı olarak "John" veya "Emma" gibi yaygın bir İngilizce isim kullanmanızı öneririz.
|
||||
|
||||
Transkript görünmeye başladığında, uyandırmak için ajanın adını yüksek sesle söyleyin (ör: "Friday").
|
||||
|
||||
Sorgunuzu net bir şekilde söyleyin.
|
||||
|
||||
Sisteme devam etmesi gerektiğini bildirmek için isteğinizi bir onay ifadesiyle bitirin. Onay ifadesi örnekleri:
|
||||
```
|
||||
"do it", "go ahead", "execute", "run", "start", "thanks", "would ya", "please", "okay?", "proceed", "continue", "go on", "do that", "go it", "do you understand?"
|
||||
```
|
||||
|
||||
## Yapılandırma
|
||||
|
||||
Örnek yapılandırma:
|
||||
```
|
||||
[MAIN]
|
||||
is_local = True
|
||||
provider_name = ollama
|
||||
provider_model = deepseek-r1:32b
|
||||
provider_server_address = http://127.0.0.1:11434 # Ollama örneği; LM-Studio için http://127.0.0.1:1234 kullanın
|
||||
agent_name = Friday
|
||||
recover_last_session = False
|
||||
save_session = False
|
||||
speak = False
|
||||
listen = False
|
||||
|
||||
jarvis_personality = False
|
||||
languages = en zh # TTS ve potansiyel yönlendirme için dil listesi.
|
||||
[BROWSER]
|
||||
headless_browser = False
|
||||
stealth_mode = False
|
||||
```
|
||||
|
||||
**`config.ini` Ayarlarının Açıklaması**:
|
||||
|
||||
* **`[MAIN]` Bölümü:**
|
||||
* `is_local`: Yerel LLM sağlayıcısı (Ollama, LM-Studio, yerel OpenAI uyumlu sunucu) veya kendi barındırdığınız sunucu seçeneğini kullanıyorsanız `True`. Bulut tabanlı API (OpenAI, Google, vb.) kullanıyorsanız `False`.
|
||||
* `provider_name`: LLM sağlayıcısını belirtir.
|
||||
* Yerel seçenekler: `ollama`, `lm-studio`, `openai` (yerel OpenAI uyumlu sunucular için), `server` (kendi barındırdığınız sunucu kurulumu için).
|
||||
* API seçenekleri: `openai`, `google`, `deepseek`, `huggingface`, `togetherAI`.
|
||||
* `provider_model`: Seçilen sağlayıcı için belirli model adı veya kimliği (ör: Ollama için `deepseekcoder:6.7b`, OpenAI API için `gpt-3.5-turbo`, TogetherAI için `mistralai/Mixtral-8x7B-Instruct-v0.1`).
|
||||
* `provider_server_address`: LLM sağlayıcınızın adresi.
|
||||
* Yerel sağlayıcılar için: ör: Ollama için `http://127.0.0.1:11434`, LM-Studio için `http://127.0.0.1:1234`.
|
||||
* `server` sağlayıcı türü için: Kendi barındırdığınız LLM sunucunuzun adresi (ör: `http://sunucu_ip_adresiniz:3333`).
|
||||
* Bulut API'leri (`is_local = False`) için: Genellikle yok sayılır veya boş bırakılabilir, çünkü API uç noktası genellikle istemci kütüphanesi tarafından işlenir.
|
||||
* `agent_name`: Yapay zeka asistanının adı (ör: Friday). Etkinleştirilmişse konuşmadan metne için tetikleyici kelime olarak kullanılır.
|
||||
* `recover_last_session`: Önceki oturumun durumunu kurtarmaya çalışmak için `True`, yeni başlamak için `False`.
|
||||
* `save_session`: Mevcut oturumun durumunu olası kurtarma için kaydetmek için `True`, aksi takdirde `False`.
|
||||
* `speak`: Metinden sese sesli çıktıyı etkinleştirmek için `True`, devre dışı bırakmak için `False`.
|
||||
* `listen`: Konuşmadan metne sesli girdiyi etkinleştirmek için `True` (yalnızca CLI modu), devre dışı bırakmak için `False`.
|
||||
* `work_dir`: **Kritik:** AgenticSeek'in dosya okuyacağı/yazacağı dizin. **Bu yolun sisteminizde geçerli ve erişilebilir olduğundan emin olun.**
|
||||
* `jarvis_personality`: Daha "Jarvis-benzeri" bir sistem istemi kullanmak için `True` (deneysel), standart istem için `False`.
|
||||
* `languages`: Virgülle ayrılmış dil listesi (ör: `en, zh, fr`). TTS ses seçimi için kullanılır (varsayılan olarak ilki) ve LLM yönlendiricisine yardımcı olabilir. Yönlendirici verimliliği için çok fazla veya çok benzer dil kullanmaktan kaçının.
|
||||
* **`[BROWSER]` Bölümü:**
|
||||
* `headless_browser`: Otomatik tarayıcıyı görünür pencere olmadan çalıştırmak için `True` (web arayüzü veya etkileşimsiz kullanım için önerilir). Tarayıcı penceresini göstermek için `False` (CLI modu veya hata ayıklama için kullanışlıdır).
|
||||
* `stealth_mode`: Tarayıcı otomasyonunun tespit edilmesini zorlaştıran önlemleri etkinleştirmek için `True`. Anticaptcha gibi tarayıcı eklentilerinin manuel olarak yüklenmesini gerektirebilir.
|
||||
|
||||
Bu bölüm desteklenen LLM sağlayıcı türlerini özetler. `config.ini` dosyasında yapılandırın.
|
||||
|
||||
**Yerel Sağlayıcılar (Kendi Donanımınızda Çalışır):**
|
||||
|
||||
| `config.ini`'deki Sağlayıcı Adı | `is_local` | Açıklama | Kurulum Bölümü |
|
||||
|-------------------------------|------------|-----------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||
| `ollama` | `True` | Ollama kullanarak yerel LLM'leri sunun. | [LLM'yi yerel olarak çalıştırma kurulumu](#llmyi-makinenizde-yerel-olarak-çalıştırma-kurulumu) |
|
||||
| `lm-studio` | `True` | LM-Studio kullanarak yerel LLM'leri sunun. | [LLM'yi yerel olarak çalıştırma kurulumu](#llmyi-makinenizde-yerel-olarak-çalıştırma-kurulumu) |
|
||||
| `openai` (yerel sunucu için) | `True` | OpenAI uyumlu API sunan yerel bir sunucuya bağlanın (ör: llama.cpp). | [LLM'yi yerel olarak çalıştırma kurulumu](#llmyi-makinenizde-yerel-olarak-çalıştırma-kurulumu) |
|
||||
| `server` | `False` | Başka bir makinede çalışan AgenticSeek kendi barındırmalı LLM sunucusuna bağlanın. | [LLM'yi kendi sunucunuzda çalıştırma kurulumu](#llmyi-kendi-sunucunuzda-çalıştırma-kurulumu) |
|
||||
|
||||
**API Sağlayıcıları (Bulut Tabanlı):**
|
||||
|
||||
| `config.ini`'deki Sağlayıcı Adı | `is_local` | Açıklama | Kurulum Bölümü |
|
||||
|-------------------------------|------------|--------------------------------------------------|-----------------------------------------------------|
|
||||
| `openai` | `False` | OpenAI'ın resmi API'sini kullanın (ör: GPT-3.5, GPT-4). | [API ile çalıştırma kurulumu](#api-ile-çalıştırma-kurulumu) |
|
||||
| `google` | `False` | Google Gemini modellerini API üzerinden kullanın. | [API ile çalıştırma kurulumu](#api-ile-çalıştırma-kurulumu) |
|
||||
| `deepseek` | `False` | Deepseek'in resmi API'sini kullanın. | [API ile çalıştırma kurulumu](#api-ile-çalıştırma-kurulumu) |
|
||||
| `huggingface` | `False` | Hugging Face Inference API'yi kullanın. | [API ile çalıştırma kurulumu](#api-ile-çalıştırma-kurulumu) |
|
||||
| `togetherAI` | `False` | TogetherAI API'si üzerinden çeşitli açık modelleri kullanın. | [API ile çalıştırma kurulumu](#api-ile-çalıştırma-kurulumu) |
|
||||
|
||||
---
|
||||
## Sorun Giderme
|
||||
|
||||
Sorunlarla karşılaşırsanız bu bölüm rehberlik sağlar.
|
||||
|
||||
# Bilinen Sorunlar
|
||||
|
||||
## ChromeDriver Sorunları
|
||||
|
||||
**Hata Örneği:** `SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version XXX`
|
||||
|
||||
### Temel Neden
|
||||
ChromeDriver sürüm uyumsuzluğu şu durumlarda oluşur:
|
||||
1. Yüklü ChromeDriver sürümünüz Chrome tarayıcı sürümünüzle eşleşmiyor
|
||||
2. Docker ortamlarında `undetected_chromedriver`, bağlanan ikili dosyayı atlayarak kendi ChromeDriver sürümünü indirebilir
|
||||
|
||||
### Çözüm Adımları
|
||||
|
||||
#### 1. Chrome Sürümünüzü Kontrol Edin
|
||||
Google Chrome'u açın → `Ayarlar > Chrome Hakkında` bölümünden sürümünüzü bulun (ör: "Sürüm 134.0.6998.88")
|
||||
|
||||
#### 2. Eşleşen ChromeDriver'ı İndirin
|
||||
|
||||
**Chrome 115 ve sonrası için:** [Chrome for Testing API](https://googlechromelabs.github.io/chrome-for-testing/) kullanın
|
||||
- Chrome for Testing uygunluk panosunu ziyaret edin
|
||||
- Chrome sürümünüzü veya en yakın eşleşmeyi bulun
|
||||
- İşletim sisteminiz için ChromeDriver'ı indirin (Docker ortamları için Linux64)
|
||||
|
||||
**Eski Chrome sürümleri için:** [Eski ChromeDriver indirmeleri](https://chromedriver.chromium.org/downloads) kullanın
|
||||
|
||||

|
||||
|
||||
#### 3. ChromeDriver'ı Yükleyin (Bir Yöntem Seçin)
|
||||
|
||||
**Yöntem A: Proje Kök Dizini (Docker için Önerilir)**
|
||||
```bash
|
||||
# İndirilen chromedriver ikili dosyasını proje kök dizinine yerleştirin
|
||||
cp path/to/downloaded/chromedriver ./chromedriver
|
||||
chmod +x ./chromedriver # Linux/macOS'ta çalıştırılabilir yapın
|
||||
```
|
||||
|
||||
**Yöntem B: Sistem PATH'i**
|
||||
```bash
|
||||
# Linux/macOS
|
||||
sudo mv chromedriver /usr/local/bin/
|
||||
sudo chmod +x /usr/local/bin/chromedriver
|
||||
|
||||
# Windows: chromedriver.exe dosyasını PATH'inizdeki bir klasöre yerleştirin
|
||||
```
|
||||
|
||||
#### 4. Kurulumu Doğrulayın
|
||||
```bash
|
||||
# ChromeDriver sürümünü test edin
|
||||
./chromedriver --version
|
||||
# Veya PATH'teyse:
|
||||
chromedriver --version
|
||||
```
|
||||
|
||||
### Docker'a Özel Notlar
|
||||
|
||||
⚠️ **Docker Kullanıcıları İçin Önemli:**
|
||||
- Docker volume mount yaklaşımı gizli modda (`undetected_chromedriver`) çalışmayabilir
|
||||
- **Çözüm**: ChromeDriver'ı proje kök dizinine `./chromedriver` olarak yerleştirin
|
||||
- Uygulama bu ikili dosyayı otomatik olarak algılayıp kullanacaktır
|
||||
- Loglarda şunu görmelisiniz: `"Using ChromeDriver from project root: ./chromedriver"`
|
||||
|
||||
### Sorun Giderme İpuçları
|
||||
|
||||
1. **Hâlâ sürüm uyumsuzluğu mu var?**
|
||||
- ChromeDriver'ın çalıştırılabilir olduğunu doğrulayın: `ls -la ./chromedriver`
|
||||
- ChromeDriver sürümünü kontrol edin: `./chromedriver --version`
|
||||
- Chrome tarayıcı sürümünüzle eşleştiğinden emin olun
|
||||
|
||||
2. **Docker container sorunları mı var?**
|
||||
- Backend loglarını kontrol edin: `docker logs backend`
|
||||
- Şu mesajı arayın: `"Using ChromeDriver from project root"`
|
||||
- Bulunamazsa dosyanın var olduğunu ve çalıştırılabilir olduğunu doğrulayın
|
||||
|
||||
3. **Chrome for Testing sürümleri**
|
||||
- Mümkün olduğunca tam sürüm eşleşmesi kullanın
|
||||
- 134.0.6998.88 sürümü için ChromeDriver 134.0.6998.165 kullanın (en yakın mevcut sürüm)
|
||||
- Ana sürüm numaraları eşleşmelidir (134 = 134)
|
||||
|
||||
### Sürüm Uyumluluk Matrisi
|
||||
|
||||
| Chrome Sürümü | ChromeDriver Sürümü | Durum |
|
||||
|----------------|---------------------|---------|
|
||||
| 134.0.6998.x | 134.0.6998.165 | ✅ Çalışır |
|
||||
| 133.0.6943.x | 133.0.6943.141 | ✅ Çalışır |
|
||||
| 132.0.6834.x | 132.0.6834.159 | ✅ Çalışır |
|
||||
|
||||
*En güncel uyumluluk için [Chrome for Testing panosunu](https://googlechromelabs.github.io/chrome-for-testing/) kontrol edin*
|
||||
|
||||
`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`
|
||||
|
||||
Bu hata, tarayıcınız ve chromedriver sürümü arasında uyumsuzluk varsa oluşur.
|
||||
|
||||
En son sürümü indirmek için şu adrese gidin:
|
||||
|
||||
https://developer.chrome.com/docs/chromedriver/downloads
|
||||
|
||||
Chrome sürüm 115 veya daha yenisini kullanıyorsanız:
|
||||
|
||||
https://googlechromelabs.github.io/chrome-for-testing/
|
||||
|
||||
adresinden işletim sisteminize uygun chromedriver sürümünü indirin.
|
||||
|
||||

|
||||
|
||||
Bu bölüm eksikse lütfen bir issue açın.
|
||||
|
||||
## Bağlantı Adaptörü Sorunları
|
||||
|
||||
```
|
||||
Exception: Provider lm-studio failed: HTTP request failed: No connection adapters were found for '127.0.0.1:1234/v1/chat/completions'` (Not: port değişebilir)
|
||||
```
|
||||
|
||||
* **Neden:** `config.ini` dosyasında `lm-studio` (veya benzeri yerel OpenAI uyumlu sunucular) için `provider_server_address` değerinde `http://` öneki eksik veya yanlış porta yönlendiriliyor.
|
||||
* **Çözüm:**
|
||||
* Adresin `http://` içerdiğinden emin olun. LM-Studio varsayılan olarak genellikle `http://127.0.0.1:1234` kullanır.
|
||||
* Doğru `config.ini`: `provider_server_address = http://127.0.0.1:1234` (veya gerçek LM-Studio sunucu portunuz).
|
||||
|
||||
## SearxNG Temel URL'si Belirtilmemiş
|
||||
|
||||
```
|
||||
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.`
|
||||
```
|
||||
|
||||
Bu hata, CLI modunu yanlış SearxNG temel URL'si ile çalıştırdığınızda oluşabilir.
|
||||
|
||||
SEARXNG_BASE_URL, Docker'da mı yoksa ana makinede mi çalıştırdığınıza göre değişmelidir:
|
||||
|
||||
**Ana makinede çalıştırma**: `SEARXNG_BASE_URL="http://localhost:8080"`
|
||||
|
||||
**Tamamen Docker'da çalıştırma (web arayüzü)**: `SEARXNG_BASE_URL="http://searxng:8080"`
|
||||
|
||||
## SSS
|
||||
|
||||
**S: Hangi donanıma ihtiyacım var?**
|
||||
|
||||
| Model Boyutu | GPU | Yorum |
|
||||
|-----------|--------|-----------------------------------------------------------|
|
||||
| 7B | 8GB VRAM | ⚠️ Önerilmez. Düşük performans, sık halüsinasyonlar ve planlayıcı ajanlar büyük olasılıkla başarısız olur. |
|
||||
| 14B | 12 GB VRAM (ör: RTX 3060) | ✅ Basit görevler için kullanılabilir. Web tarama ve planlama görevlerinde zorlanabilir. |
|
||||
| 32B | 24+ GB VRAM (ör: RTX 4090) | 🚀 Çoğu görevde başarılı, görev planlamasında hâlâ zorlanabilir |
|
||||
| 70B+ | 48+ GB VRAM | 💪 Mükemmel. Gelişmiş kullanım senaryoları için önerilir. |
|
||||
|
||||
**S: Hata alıyorum, ne yapmalıyım?**
|
||||
|
||||
Yerel sunucunun çalıştığından (`ollama serve`), `config.ini` dosyanızın sağlayıcınızla eşleştiğinden ve bağımlılıkların yüklü olduğundan emin olun. Hiçbiri işe yaramazsa bir issue açmaktan çekinmeyin.
|
||||
|
||||
**S: Gerçekten %100 yerel çalışabilir mi?**
|
||||
|
||||
Evet, Ollama, lm-studio veya server sağlayıcıları ile tüm konuşmadan metne, LLM ve metinden sese modelleri yerel olarak çalışır. Yerel olmayan seçenekler (OpenAI veya diğer API'ler) isteğe bağlıdır.
|
||||
|
||||
**S: Manus varken neden AgenticSeek kullanmalıyım?**
|
||||
|
||||
Manus'un aksine, AgenticSeek harici sistemlerden bağımsızlığa öncelik verir, size daha fazla kontrol, gizlilik ve API maliyetinden kaçınma imkânı sunar.
|
||||
|
||||
**S: Projenin arkasında kim var?**
|
||||
|
||||
Proje benim tarafımdan, bakımcı olarak görev yapan iki arkadaşım ve GitHub'daki açık kaynak topluluğundan katkıda bulunanlarla birlikte oluşturuldu. Bir startup veya herhangi bir kuruluşla bağlantılı değiliz, sadece tutkulu bireylerden oluşan bir grubuz.
|
||||
|
||||
X'te kişisel hesabım (https://x.com/Martin993886460) dışındaki herhangi bir AgenticSeek hesabı taklittir.
|
||||
|
||||
## Katkıda Bulunma
|
||||
|
||||
AgenticSeek'i geliştirmek için geliştiriciler arıyoruz! Açık issue'lara veya tartışmalara göz atın.
|
||||
|
||||
[Katkıda bulunma rehberi](./docs/CONTRIBUTING.md)
|
||||
|
||||
|
||||
## Sponsorlar:
|
||||
|
||||
AgenticSeek'in yeteneklerini uçuş arama, seyahat planlama veya en iyi alışveriş fırsatlarını yakalama gibi özelliklerle geliştirmek ister misiniz? Daha fazla Jarvis benzeri yetenek açmak için SerpApi ile özel bir araç oluşturmayı düşünün. SerpApi ile, tam kontrolü elinizde tutarken ajanınızı özel görevler için güçlendirebilirsiniz.
|
||||
|
||||
<a href="https://serpapi.com/"><img src="./media/banners/sponsor_banner_serpapi.png" height="350" alt="SerpApi Banner" ></a>
|
||||
|
||||
Özel araçları nasıl entegre edeceğinizi öğrenmek için [Contributing.md](./docs/CONTRIBUTING.md) dosyasına bakın!
|
||||
|
||||
### **Patron Sponsor**:
|
||||
|
||||
- [tatra-labs](https://github.com/tatra-labs)
|
||||
|
||||
## Bakımcılar:
|
||||
|
||||
> [Fosowl](https://github.com/Fosowl) | Paris Saati
|
||||
|
||||
> [antoineVIVIES](https://github.com/antoineVIVIES) | Taipei Saati
|
||||
|
||||
## Özel Teşekkürler:
|
||||
|
||||
> [tcsenpai](https://github.com/tcsenpai) ve [plitc](https://github.com/plitc) Backend dockerizasyonuna yardımları için
|
||||
|
||||
[](https://www.star-history.com/#Fosowl/agenticSeek&Date)
|
||||
@@ -278,8 +278,11 @@ async def process_query(request: QueryRequest):
|
||||
return JSONResponse(status_code=200, content=query_resp.jsonify())
|
||||
except Exception as e:
|
||||
logger.error(f"An error occurred: {str(e)}")
|
||||
sys.exit(1)
|
||||
query_resp.answer = f"An error occurred: {str(e)}"
|
||||
query_resp.reasoning = f"Error: {str(e)}"
|
||||
return JSONResponse(status_code=500, content=query_resp.jsonify())
|
||||
finally:
|
||||
is_generating = False
|
||||
logger.info("Processing finished")
|
||||
if config.getboolean('MAIN', 'save_session'):
|
||||
interaction.save_session()
|
||||
@@ -296,4 +299,4 @@ if __name__ == "__main__":
|
||||
port = int(envport)
|
||||
else:
|
||||
port = 7777
|
||||
uvicorn.run(api, host="0.0.0.0", port=7777)
|
||||
uvicorn.run(api, host="0.0.0.0", port=port)
|
||||
+1
-2
@@ -1,4 +1,3 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
redis:
|
||||
@@ -65,7 +64,7 @@ services:
|
||||
environment:
|
||||
- NODE_ENV=development
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
- REACT_APP_BACKEND_URL=http://localhost:7777
|
||||
- REACT_APP_BACKEND_URL=${REACT_APP_BACKEND_URL:-http://localhost:7777}
|
||||
networks:
|
||||
- agentic-seek-net
|
||||
|
||||
|
||||
@@ -2,15 +2,18 @@ FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
# Copy package files
|
||||
COPY agentic-seek-front/package.json agentic-seek-front/package-lock.json ./
|
||||
RUN npm install
|
||||
|
||||
# Install dependencies with explicit bin linking
|
||||
RUN npm ci && npm rebuild
|
||||
|
||||
# Copy application code
|
||||
COPY agentic-seek-front/ .
|
||||
|
||||
# Expose port
|
||||
# Verify react-scripts is available (catches install issues early)
|
||||
RUN test -f node_modules/.bin/react-scripts || npm install react-scripts
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
# Run the application
|
||||
CMD ["npm", "start"]
|
||||
+3
-2
@@ -1,4 +1,3 @@
|
||||
kokoro==0.9.4
|
||||
certifi==2025.4.26
|
||||
fastapi>=0.115.12
|
||||
flask>=3.1.0
|
||||
@@ -19,7 +18,6 @@ transformers>=4.46.3
|
||||
torch>=2.4.1
|
||||
ollama>=0.4.7
|
||||
scipy>=1.9.3
|
||||
soundfile>=0.13.1
|
||||
protobuf>=3.20.3
|
||||
termcolor>=2.4.0
|
||||
pypdf>=5.4.0
|
||||
@@ -46,3 +44,6 @@ openai
|
||||
sniffio
|
||||
ordered_set
|
||||
pypinyin
|
||||
|
||||
# Optional: TTS support (requires Python <3.12)
|
||||
# pip install kokoro==0.9.4 soundfile ipython
|
||||
|
||||
@@ -76,7 +76,12 @@ class PlannerAgent(Agent):
|
||||
if blocks == None:
|
||||
return []
|
||||
for block in blocks:
|
||||
try:
|
||||
line_json = json.loads(block)
|
||||
except json.JSONDecodeError as e:
|
||||
self.logger.warning(f"Failed to parse JSON block: {e}")
|
||||
pretty_print(f"JSON parsing error: {e}", color="warning")
|
||||
return []
|
||||
if 'plan' in line_json:
|
||||
for task in line_json['plan']:
|
||||
if task['agent'].lower() not in [ag_name.lower() for ag_name in self.agents.keys()]:
|
||||
@@ -142,17 +147,23 @@ class PlannerAgent(Agent):
|
||||
pretty_print(f"{task['agent']} -> {task['task']}", color="info")
|
||||
pretty_print("▔▗ E N D ▖▔", color="status")
|
||||
|
||||
async def make_plan(self, prompt: str) -> str:
|
||||
async def make_plan(self, prompt: str, max_retries: int = 4) -> str:
|
||||
"""
|
||||
Asks the LLM to make a plan.
|
||||
Args:
|
||||
prompt (str): The prompt to be sent to the LLM.
|
||||
max_retries (int): Maximum number of retries before giving up.
|
||||
Returns:
|
||||
str: The plan made by the LLM.
|
||||
"""
|
||||
ok = False
|
||||
answer = None
|
||||
retries = 0
|
||||
while not ok:
|
||||
if retries >= max_retries:
|
||||
pretty_print(f"Failed to make a plan after {max_retries} attempts. Giving up.", color="failure")
|
||||
self.logger.warning(f"make_plan exceeded max retries ({max_retries}).")
|
||||
return []
|
||||
animate_thinking("Thinking...", color="status")
|
||||
self.memory.push('user', prompt)
|
||||
answer, reasoning = await self.llm_request()
|
||||
@@ -163,6 +174,7 @@ class PlannerAgent(Agent):
|
||||
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")
|
||||
retries += 1
|
||||
continue
|
||||
self.show_plan(agents_tasks, answer)
|
||||
ok = True
|
||||
|
||||
+76
-6
@@ -2,7 +2,7 @@ from selenium import webdriver
|
||||
from selenium.webdriver.chrome.service import Service
|
||||
from selenium.webdriver.chrome.options import Options
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support.ui import WebDriverWait, Select
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.common.exceptions import TimeoutException, WebDriverException
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
@@ -15,11 +15,13 @@ import undetected_chromedriver as uc
|
||||
import chromedriver_autoinstaller
|
||||
import certifi
|
||||
import ssl
|
||||
import subprocess
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
import socket
|
||||
import tempfile
|
||||
import markdownify
|
||||
import sys
|
||||
@@ -76,20 +78,51 @@ def get_random_user_agent() -> str:
|
||||
]
|
||||
return random.choice(user_agents)
|
||||
|
||||
def get_chromedriver_version(chromedriver_path: str) -> str:
|
||||
"""Get the major version of a chromedriver binary. Returns empty string on failure."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[chromedriver_path, "--version"],
|
||||
capture_output=True, text=True, timeout=10
|
||||
)
|
||||
# Output format: "ChromeDriver 125.0.6422.78 (...)"
|
||||
return result.stdout.strip().split()[1].split('.')[0]
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
def is_chromedriver_compatible(chromedriver_path: str) -> bool:
|
||||
"""Check if a chromedriver binary is compatible with the installed Chrome version."""
|
||||
try:
|
||||
chrome_version = chromedriver_autoinstaller.get_chrome_version()
|
||||
if not chrome_version:
|
||||
return True # Can't determine Chrome version, assume compatible
|
||||
chrome_major = chrome_version.split('.')[0]
|
||||
driver_major = get_chromedriver_version(chromedriver_path)
|
||||
if not driver_major:
|
||||
return True # Can't determine driver version, assume compatible
|
||||
return chrome_major == driver_major
|
||||
except Exception:
|
||||
return True # On any error, assume compatible to avoid blocking
|
||||
|
||||
def install_chromedriver() -> str:
|
||||
"""
|
||||
Install the ChromeDriver if not already installed. Return the path.
|
||||
Automatically updates the driver if the version does not match the installed Chrome.
|
||||
"""
|
||||
# First try to use chromedriver in the project root directory (as per README)
|
||||
project_root_chromedriver = "./chromedriver"
|
||||
if os.path.exists(project_root_chromedriver) and os.access(project_root_chromedriver, os.X_OK):
|
||||
if is_chromedriver_compatible(project_root_chromedriver):
|
||||
print(f"Using ChromeDriver from project root: {project_root_chromedriver}")
|
||||
return project_root_chromedriver
|
||||
print("ChromeDriver in project root is outdated, attempting auto-update...")
|
||||
|
||||
# Then try to use the system-installed chromedriver
|
||||
chromedriver_path = shutil.which("chromedriver")
|
||||
if chromedriver_path:
|
||||
if is_chromedriver_compatible(chromedriver_path):
|
||||
return chromedriver_path
|
||||
print(f"System ChromeDriver at {chromedriver_path} is outdated, attempting auto-update...")
|
||||
|
||||
# In Docker environment, try the fixed path
|
||||
if os.path.exists('/.dockerenv'):
|
||||
@@ -98,9 +131,9 @@ def install_chromedriver() -> str:
|
||||
print(f"Using Docker ChromeDriver at {docker_chromedriver_path}")
|
||||
return docker_chromedriver_path
|
||||
|
||||
# Fallback to auto-installer only if no other option works
|
||||
# Auto-install matching ChromeDriver version
|
||||
try:
|
||||
print("ChromeDriver not found, attempting to install automatically...")
|
||||
print("Installing matching ChromeDriver version automatically...")
|
||||
chromedriver_path = chromedriver_autoinstaller.install()
|
||||
except Exception as e:
|
||||
raise FileNotFoundError(
|
||||
@@ -121,6 +154,12 @@ def bypass_ssl() -> str:
|
||||
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 get_free_port() -> int:
|
||||
"""Find and return a free TCP port on the local machine."""
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(('', 0))
|
||||
return s.getsockname()[1]
|
||||
|
||||
def create_chrome_options(headless=False, stealth_mode=True, crx_path="./crx/nopecha.crx", lang="en") -> Options:
|
||||
"""Create Chrome options - separated for reusability."""
|
||||
chrome_options = Options()
|
||||
@@ -147,7 +186,7 @@ def create_chrome_options(headless=False, stealth_mode=True, crx_path="./crx/nop
|
||||
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(f'--remote-debugging-port={get_free_port()}')
|
||||
chrome_options.add_argument('--disable-background-timer-throttling')
|
||||
chrome_options.add_argument('--disable-backgrounding-occluded-windows')
|
||||
chrome_options.add_argument('--disable-renderer-backgrounding')
|
||||
@@ -498,7 +537,16 @@ class Browser:
|
||||
if input_type in ["hidden", "submit", "button", "image"] or not element["displayed"]:
|
||||
continue
|
||||
input_name = element.get("text") or element.get("id") or input_type
|
||||
if input_type == "checkbox" or input_type == "radio":
|
||||
if input_type == "select":
|
||||
options = element.get("options", [])
|
||||
options_str = ", ".join(opt["text"] for opt in options if opt["text"])
|
||||
selected = next((opt["text"] for opt in options if opt.get("selected")), "")
|
||||
form_strings.append(f"[{input_name}](select: {selected}) options: [{options_str}]")
|
||||
elif input_type == "textarea":
|
||||
form_strings.append(f"[{input_name}]("")")
|
||||
elif input_type == "file":
|
||||
form_strings.append(f"[{input_name}](file: )")
|
||||
elif input_type == "checkbox" or input_type == "radio":
|
||||
try:
|
||||
checked_status = "checked" if element.is_selected() else "unchecked"
|
||||
except Exception as e:
|
||||
@@ -662,7 +710,29 @@ class Browser:
|
||||
self.logger.warning(f"Element '{name}' is not interactable (not displayed or disabled)")
|
||||
continue
|
||||
input_type = (element.get_attribute("type") or "text").lower()
|
||||
if input_type in ["checkbox", "radio"]:
|
||||
tag_name = (element.tag_name or "").lower()
|
||||
if tag_name == "select":
|
||||
try:
|
||||
select = Select(element)
|
||||
select.select_by_visible_text(value)
|
||||
self.logger.info(f"Selected '{value}' for {name}")
|
||||
except Exception:
|
||||
try:
|
||||
select.select_by_value(value)
|
||||
self.logger.info(f"Selected value '{value}' for {name}")
|
||||
except Exception as sel_e:
|
||||
self.logger.warning(f"Could not select '{value}' for {name}: {sel_e}")
|
||||
elif tag_name == "textarea":
|
||||
element.clear()
|
||||
element.send_keys(value)
|
||||
self.logger.info(f"Filled textarea {name}")
|
||||
elif input_type == "file":
|
||||
if os.path.isabs(value) and os.path.exists(value):
|
||||
element.send_keys(value)
|
||||
self.logger.info(f"Uploaded file '{value}' for {name}")
|
||||
else:
|
||||
self.logger.warning(f"File not found: {value}")
|
||||
elif input_type in ["checkbox", "radio"]:
|
||||
is_checked = element.is_selected()
|
||||
should_be_checked = value.lower() == "checked"
|
||||
|
||||
|
||||
+59
-10
@@ -17,6 +17,9 @@ 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):
|
||||
self.provider_name = provider_name.lower()
|
||||
# Normalize provider name aliases (e.g. README documents 'togetherAI' but canonical key is 'together')
|
||||
_aliases = {"togetherai": "together"}
|
||||
self.provider_name = _aliases.get(self.provider_name, self.provider_name)
|
||||
self.model = model
|
||||
self.is_local = is_local
|
||||
self.server_ip = server_address
|
||||
@@ -32,12 +35,14 @@ class Provider:
|
||||
"together": self.together_fn,
|
||||
"dsk_deepseek": self.dsk_deepseek,
|
||||
"openrouter": self.openrouter_fn,
|
||||
"anthropic": self.anthropic_fn,
|
||||
"minimax": self.minimax_fn,
|
||||
"test": self.test_fn
|
||||
}
|
||||
self.logger = Logger("provider.log")
|
||||
self.api_key = None
|
||||
self.internal_url, self.in_docker = self.get_internal_url()
|
||||
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google", "openrouter"]
|
||||
self.unsafe_providers = ["openai", "deepseek", "dsk_deepseek", "together", "google", "openrouter", "anthropic", "minimax"]
|
||||
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:
|
||||
@@ -160,7 +165,11 @@ class Provider:
|
||||
Use local or remote Ollama server to generate text.
|
||||
"""
|
||||
thought = ""
|
||||
host = f"{self.internal_url}:11434" if self.is_local else f"http://{self.server_address}"
|
||||
if self.is_local:
|
||||
server_port = self.server_address.split(":")[-1] if ":" in str(self.server_address) else "11434"
|
||||
host = f"{self.internal_url}:{server_port}"
|
||||
else:
|
||||
host = f"http://{self.server_address}"
|
||||
client = OllamaClient(host=host)
|
||||
|
||||
try:
|
||||
@@ -339,13 +348,21 @@ class Provider:
|
||||
Use local lm-studio server to generate text.
|
||||
"""
|
||||
if self.in_docker:
|
||||
# Extract port from server_address if present
|
||||
# Extract port from server_address, handling both "host:port" and "http://host:port"
|
||||
port = "1234" # default
|
||||
if ":" in self.server_address:
|
||||
port = self.server_address.split(":")[1]
|
||||
addr = self.server_address
|
||||
if "://" not in addr:
|
||||
addr = f"http://{addr}"
|
||||
parsed_addr = urlparse(addr)
|
||||
if parsed_addr.port:
|
||||
port = str(parsed_addr.port)
|
||||
url = f"{self.internal_url}:{port}"
|
||||
else:
|
||||
url = f"http://{self.server_ip}"
|
||||
# Normalize the address to ensure it has a scheme prefix
|
||||
addr = self.server_ip
|
||||
if "://" not in addr:
|
||||
addr = f"http://{addr}"
|
||||
url = addr
|
||||
route_start = f"{url}/v1/chat/completions"
|
||||
payload = {
|
||||
"messages": history,
|
||||
@@ -387,7 +404,6 @@ class Provider:
|
||||
if "LM Studio" in str(e):
|
||||
raise # Re-raise our custom exceptions
|
||||
raise Exception(f"Unexpected error: {str(e)}") from e
|
||||
return thought
|
||||
|
||||
def openrouter_fn(self, history, verbose=False):
|
||||
"""
|
||||
@@ -412,6 +428,39 @@ class Provider:
|
||||
except Exception as e:
|
||||
raise Exception(f"OpenRouter API error: {str(e)}") from e
|
||||
|
||||
def minimax_fn(self, history, verbose=False):
|
||||
"""
|
||||
Use MiniMax API to generate text via OpenAI-compatible interface.
|
||||
|
||||
Supported models:
|
||||
- MiniMax-M2.7: Latest flagship model with enhanced reasoning and coding
|
||||
- MiniMax-M2.7-highspeed: High-speed version of M2.7 for low-latency scenarios
|
||||
- MiniMax-M2.5: Peak performance model (~60 tps), 204,800 context window
|
||||
- MiniMax-M2.5-highspeed: Same performance, faster (~100 tps)
|
||||
|
||||
Note: temperature must be in range (0.0, 1.0], default is 1.0
|
||||
"""
|
||||
load_dotenv()
|
||||
base_url = os.getenv("MINIMAX_BASE_URL", "https://api.minimax.io/v1")
|
||||
|
||||
client = OpenAI(api_key=self.api_key, base_url=base_url)
|
||||
if self.is_local:
|
||||
raise Exception("MiniMax is not available for local use. Change config.ini")
|
||||
try:
|
||||
response = client.chat.completions.create(
|
||||
model=self.model,
|
||||
messages=history,
|
||||
temperature=1.0,
|
||||
)
|
||||
if response is None:
|
||||
raise Exception("MiniMax response is empty.")
|
||||
thought = response.choices[0].message.content
|
||||
if verbose:
|
||||
print(thought)
|
||||
return thought
|
||||
except Exception as e:
|
||||
raise Exception(f"MiniMax API error: {str(e)}") from e
|
||||
|
||||
def dsk_deepseek(self, history, verbose=False):
|
||||
"""
|
||||
Use: xtekky/deepseek4free
|
||||
@@ -436,13 +485,13 @@ class Provider:
|
||||
if chunk['type'] == 'text':
|
||||
thought += chunk['content']
|
||||
return thought
|
||||
except AuthenticationError:
|
||||
except AuthenticationError as e:
|
||||
raise AuthenticationError("Authentication failed. Please check your token.") from e
|
||||
except RateLimitError:
|
||||
except RateLimitError as e:
|
||||
raise RateLimitError("Rate limit exceeded. Please wait before making more requests.") from e
|
||||
except CloudflareError as e:
|
||||
raise CloudflareError(f"Cloudflare protection encountered: {str(e)}") from e
|
||||
except NetworkError:
|
||||
except NetworkError as e:
|
||||
raise NetworkError("Network error occurred. Check your internet connection.") from e
|
||||
except APIError as e:
|
||||
raise APIError(f"API error occurred: {str(e)}") from e
|
||||
|
||||
@@ -11,7 +11,8 @@ try:
|
||||
from IPython.display import display, Audio
|
||||
import soundfile as sf
|
||||
except ImportError:
|
||||
print("Speech synthesis disabled. Please install the kokoro package.")
|
||||
print("Speech synthesis disabled. To enable TTS, install: pip install kokoro==0.9.4 soundfile ipython")
|
||||
print("Note: kokoro requires Python <3.12 due to num2words dependency.")
|
||||
IMPORT_FOUND = False
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import sys
|
||||
import os
|
||||
from urllib.parse import urlencode
|
||||
|
||||
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__)))))
|
||||
@@ -16,7 +18,7 @@ class searxSearch(Tools):
|
||||
self.tag = "web_search"
|
||||
self.name = "searxSearch"
|
||||
self.description = "A tool for searching a SearxNG for web search"
|
||||
self.base_url = os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
|
||||
self.base_url = base_url or os.getenv("SEARXNG_BASE_URL") # Requires a SearxNG base URL
|
||||
self.user_agent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36"
|
||||
self.paywall_keywords = [
|
||||
"Member-only", "access denied", "restricted content", "404", "this page is not working"
|
||||
@@ -77,7 +79,14 @@ class searxSearch(Tools):
|
||||
'Upgrade-Insecure-Requests': '1',
|
||||
'User-Agent': self.user_agent
|
||||
}
|
||||
data = f"q={query}&categories=general&language=auto&time_range=&safesearch=0&theme=simple".encode('utf-8')
|
||||
data = urlencode({
|
||||
'q': query,
|
||||
'categories': 'general',
|
||||
'language': 'auto',
|
||||
'time_range': '',
|
||||
'safesearch': '0',
|
||||
'theme': 'simple'
|
||||
}).encode('utf-8')
|
||||
try:
|
||||
response = requests.post(search_url, headers=headers, data=data, verify=False)
|
||||
response.raise_for_status()
|
||||
@@ -101,7 +110,7 @@ class searxSearch(Tools):
|
||||
"""
|
||||
Checks if the execution failed based on the output.
|
||||
"""
|
||||
return "Error" in output
|
||||
return "Error" in output or "No search results" in output
|
||||
|
||||
def interpreter_feedback(self, output: str) -> str:
|
||||
"""
|
||||
|
||||
@@ -11,6 +11,37 @@ function findInputs(element, result = []) {
|
||||
displayed: isElementDisplayed(input)
|
||||
});
|
||||
});
|
||||
// Find all <select> elements (dropdowns / multi-choice)
|
||||
const selects = element.querySelectorAll('select');
|
||||
selects.forEach(select => {
|
||||
const options = Array.from(select.options).map(opt => ({
|
||||
value: opt.value,
|
||||
text: opt.textContent.trim(),
|
||||
selected: opt.selected
|
||||
}));
|
||||
result.push({
|
||||
tagName: select.tagName,
|
||||
text: select.name || '',
|
||||
type: 'select',
|
||||
class: select.className || '',
|
||||
xpath: getXPath(select),
|
||||
displayed: isElementDisplayed(select),
|
||||
multiple: select.multiple,
|
||||
options: options
|
||||
});
|
||||
});
|
||||
// Find all <textarea> elements
|
||||
const textareas = element.querySelectorAll('textarea');
|
||||
textareas.forEach(textarea => {
|
||||
result.push({
|
||||
tagName: textarea.tagName,
|
||||
text: textarea.name || '',
|
||||
type: 'textarea',
|
||||
class: textarea.className || '',
|
||||
xpath: getXPath(textarea),
|
||||
displayed: isElementDisplayed(textarea)
|
||||
});
|
||||
});
|
||||
const allElements = element.querySelectorAll('*');
|
||||
allElements.forEach(el => {
|
||||
if (el.shadowRoot) {
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ max_size_bytes=$((2 * 1024 * 1024 * 1024 * 10))
|
||||
|
||||
echo "Mounting $WORK_DIR ($dir_size_bytes bytes) to docker."
|
||||
|
||||
if [ "$dir_size_bytes" -gt "$max_size_bytes" ]; then
|
||||
if [ -n "$dir_size_bytes" ] && [ "$dir_size_bytes" -gt "$max_size_bytes" ]; then
|
||||
echo "Error: WORK_DIR ($WORK_DIR) contains more than 20GB of data ($(du -sh "$WORK_DIR" 2>/dev/null | awk '{print $1}'))."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -1,20 +1,37 @@
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Add project root to Python path
|
||||
|
||||
# Mock heavy dependencies
|
||||
for mod_name in [
|
||||
'torch', 'transformers', 'kokoro', 'adaptive_classifier', 'text2emotion',
|
||||
'ollama', 'openai', 'together', 'IPython', 'IPython.display',
|
||||
'playsound3', 'soundfile', 'pyaudio', 'librosa',
|
||||
'pypdf', 'langid', 'pypinyin', 'fake_useragent',
|
||||
'chromedriver_autoinstaller', 'num2words', 'sentencepiece', 'sacremoses',
|
||||
'scipy', 'numpy', 'selenium_stealth', 'undetected_chromedriver',
|
||||
'markdownify',
|
||||
]:
|
||||
if mod_name not in sys.modules:
|
||||
sys.modules[mod_name] = MagicMock()
|
||||
|
||||
os.environ.setdefault('WORK_DIR', '/tmp')
|
||||
|
||||
from sources.agents.browser_agent import BrowserAgent
|
||||
|
||||
class TestBrowserAgentParsing(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# Initialize a basic BrowserAgent instance for testing
|
||||
self.agent = BrowserAgent(
|
||||
name="TestAgent",
|
||||
prompt_path="../prompts/base/browser_agent.txt",
|
||||
provider=None
|
||||
)
|
||||
self.agent = BrowserAgent.__new__(BrowserAgent)
|
||||
self.agent.notes = []
|
||||
self.agent.navigable_links = []
|
||||
self.agent.search_history = []
|
||||
self.agent.current_page = ""
|
||||
self.agent.logger = MagicMock()
|
||||
|
||||
def test_extract_links(self):
|
||||
# Test various link formats
|
||||
test_text = """
|
||||
Check this out: https://thriveonai.com/15-ai-startups-in-japan-to-take-note-of, and www.google.com!
|
||||
Also try https://test.org/about?page=1, hey this one as well bro https://weatherstack.com/documentation/.
|
||||
@@ -28,8 +45,17 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
||||
result = self.agent.extract_links(test_text)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_extract_links_no_links(self):
|
||||
"""Test that text without links returns empty list."""
|
||||
result = self.agent.extract_links("No links here at all.")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_extract_links_single_link(self):
|
||||
"""Test extraction of a single link."""
|
||||
result = self.agent.extract_links("Visit https://example.com for details")
|
||||
self.assertEqual(result, ["https://example.com"])
|
||||
|
||||
def test_extract_form(self):
|
||||
# Test form extraction
|
||||
test_text = """
|
||||
Fill this: [username](john) and [password](secret123)
|
||||
Not a form: [random]text
|
||||
@@ -38,8 +64,18 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
||||
result = self.agent.extract_form(test_text)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_extract_form_empty(self):
|
||||
"""Test form extraction with no form inputs."""
|
||||
result = self.agent.extract_form("Just regular text here.")
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_extract_form_checkbox(self):
|
||||
"""Test form extraction with checkbox values."""
|
||||
text = "[agree](checked) and [newsletter](unchecked)"
|
||||
result = self.agent.extract_form(text)
|
||||
self.assertEqual(len(result), 2)
|
||||
|
||||
def test_clean_links(self):
|
||||
# Test link cleaning
|
||||
test_links = [
|
||||
"https://example.com.",
|
||||
"www.test.com,",
|
||||
@@ -55,8 +91,13 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
||||
result = self.agent.clean_links(test_links)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_clean_links_with_slash(self):
|
||||
"""Test that trailing slash is stripped since it's not alphanumeric."""
|
||||
links = ["https://example.com/path/"]
|
||||
result = self.agent.clean_links(links)
|
||||
self.assertEqual(result, ["https://example.com/path"])
|
||||
|
||||
def test_parse_answer(self):
|
||||
# Test parsing answer with notes and links
|
||||
test_text = """
|
||||
Here's some info
|
||||
Note: This is important. We are doing test it's very cool.
|
||||
@@ -66,5 +107,102 @@ class TestBrowserAgentParsing(unittest.TestCase):
|
||||
self.agent.parse_answer(test_text)
|
||||
self.assertEqual(self.agent.notes[0], "Note: This is important. We are doing test it's very cool.")
|
||||
|
||||
def test_parse_answer_extracts_links(self):
|
||||
"""Test that parse_answer returns extracted links."""
|
||||
text = "Navigate to https://example.com and https://test.org"
|
||||
links = self.agent.parse_answer(text)
|
||||
self.assertIn("https://example.com", links)
|
||||
self.assertIn("https://test.org", links)
|
||||
|
||||
def test_parse_answer_no_notes(self):
|
||||
"""Test parse_answer with no notes section."""
|
||||
text = "Go to https://example.com"
|
||||
self.agent.parse_answer(text)
|
||||
# Notes should have an empty entry
|
||||
self.assertEqual(len(self.agent.notes), 1)
|
||||
|
||||
def test_select_link_unvisited(self):
|
||||
"""Test selecting first unvisited link."""
|
||||
self.agent.search_history = ["https://visited.com"]
|
||||
self.agent.current_page = "https://current.com"
|
||||
links = ["https://visited.com", "https://current.com", "https://new.com"]
|
||||
result = self.agent.select_link(links)
|
||||
self.assertEqual(result, "https://new.com")
|
||||
|
||||
def test_select_link_all_visited(self):
|
||||
"""Test that None is returned when all links are visited."""
|
||||
self.agent.search_history = ["https://a.com", "https://b.com"]
|
||||
self.agent.current_page = ""
|
||||
links = ["https://a.com", "https://b.com"]
|
||||
result = self.agent.select_link(links)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_select_link_empty(self):
|
||||
"""Test with empty links list."""
|
||||
result = self.agent.select_link([])
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_jsonify_search_results(self):
|
||||
"""Test parsing search result text into structured data."""
|
||||
text = """Title: Result One
|
||||
Snippet: First result snippet
|
||||
Link: https://one.com
|
||||
|
||||
Title: Result Two
|
||||
Snippet: Second result snippet
|
||||
Link: https://two.com"""
|
||||
results = self.agent.jsonify_search_results(text)
|
||||
self.assertEqual(len(results), 2)
|
||||
self.assertEqual(results[0]["title"], "Result One")
|
||||
self.assertEqual(results[0]["link"], "https://one.com")
|
||||
self.assertEqual(results[1]["snippet"], "Second result snippet")
|
||||
|
||||
def test_jsonify_search_results_empty(self):
|
||||
"""Test with empty search results."""
|
||||
results = self.agent.jsonify_search_results("")
|
||||
self.assertEqual(results, [])
|
||||
|
||||
def test_jsonify_search_results_partial(self):
|
||||
"""Test with partial result (only title and link)."""
|
||||
text = """Title: Partial Result
|
||||
Link: https://partial.com"""
|
||||
results = self.agent.jsonify_search_results(text)
|
||||
self.assertEqual(len(results), 1)
|
||||
self.assertEqual(results[0]["title"], "Partial Result")
|
||||
self.assertNotIn("snippet", results[0])
|
||||
|
||||
def test_stringify_search_results(self):
|
||||
"""Test converting structured results back to string."""
|
||||
results = [
|
||||
{"link": "https://one.com", "snippet": "First snippet"},
|
||||
{"link": "https://two.com", "snippet": "Second snippet"}
|
||||
]
|
||||
output = self.agent.stringify_search_results(results)
|
||||
self.assertIn("https://one.com", output)
|
||||
self.assertIn("First snippet", output)
|
||||
self.assertIn("https://two.com", output)
|
||||
|
||||
def test_select_unvisited(self):
|
||||
"""Test filtering visited results."""
|
||||
self.agent.search_history = ["https://visited.com"]
|
||||
results = [
|
||||
{"link": "https://visited.com", "title": "Old"},
|
||||
{"link": "https://new.com", "title": "New"}
|
||||
]
|
||||
unvisited = self.agent.select_unvisited(results)
|
||||
self.assertEqual(len(unvisited), 1)
|
||||
self.assertEqual(unvisited[0]["link"], "https://new.com")
|
||||
|
||||
def test_select_unvisited_all_new(self):
|
||||
"""Test when no results are visited."""
|
||||
self.agent.search_history = []
|
||||
results = [
|
||||
{"link": "https://a.com", "title": "A"},
|
||||
{"link": "https://b.com", "title": "B"}
|
||||
]
|
||||
unvisited = self.agent.select_unvisited(results)
|
||||
self.assertEqual(len(unvisited), 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,82 @@
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
# Mock heavy dependencies
|
||||
for mod_name in [
|
||||
'torch', 'transformers', 'kokoro', 'adaptive_classifier', 'text2emotion',
|
||||
'ollama', 'openai', 'together', 'IPython', 'IPython.display',
|
||||
'playsound3', 'soundfile', 'pyaudio', 'librosa',
|
||||
'pypdf', 'langid', 'pypinyin', 'fake_useragent',
|
||||
'num2words', 'sentencepiece', 'sacremoses',
|
||||
'scipy', 'numpy', 'selenium_stealth', 'undetected_chromedriver',
|
||||
'markdownify', 'chromedriver_autoinstaller',
|
||||
]:
|
||||
if mod_name not in sys.modules:
|
||||
sys.modules[mod_name] = MagicMock()
|
||||
|
||||
os.environ.setdefault('WORK_DIR', '/tmp')
|
||||
|
||||
from sources.browser import get_chromedriver_version, is_chromedriver_compatible
|
||||
|
||||
|
||||
class TestChromedriverVersionCheck(unittest.TestCase):
|
||||
"""Test suite for ChromeDriver version checking and auto-update logic."""
|
||||
|
||||
@patch('sources.browser.subprocess.run')
|
||||
def test_get_chromedriver_version_success(self, mock_run):
|
||||
"""Test extracting major version from chromedriver --version output."""
|
||||
mock_run.return_value = MagicMock(
|
||||
stdout="ChromeDriver 125.0.6422.78 (abc123)\n"
|
||||
)
|
||||
self.assertEqual(get_chromedriver_version("/usr/bin/chromedriver"), "125")
|
||||
|
||||
@patch('sources.browser.subprocess.run')
|
||||
def test_get_chromedriver_version_failure(self, mock_run):
|
||||
"""Test graceful failure when chromedriver --version fails."""
|
||||
mock_run.side_effect = FileNotFoundError("not found")
|
||||
self.assertEqual(get_chromedriver_version("/nonexistent"), "")
|
||||
|
||||
@patch('sources.browser.subprocess.run')
|
||||
def test_get_chromedriver_version_timeout(self, mock_run):
|
||||
"""Test graceful failure on timeout."""
|
||||
import subprocess
|
||||
mock_run.side_effect = subprocess.TimeoutExpired(cmd="chromedriver", timeout=10)
|
||||
self.assertEqual(get_chromedriver_version("/usr/bin/chromedriver"), "")
|
||||
|
||||
@patch('sources.browser.chromedriver_autoinstaller.get_chrome_version')
|
||||
@patch('sources.browser.get_chromedriver_version')
|
||||
def test_compatible_versions(self, mock_driver_ver, mock_chrome_ver):
|
||||
"""Test that matching major versions are compatible."""
|
||||
mock_chrome_ver.return_value = "125.0.6422.78"
|
||||
mock_driver_ver.return_value = "125"
|
||||
self.assertTrue(is_chromedriver_compatible("/usr/bin/chromedriver"))
|
||||
|
||||
@patch('sources.browser.chromedriver_autoinstaller.get_chrome_version')
|
||||
@patch('sources.browser.get_chromedriver_version')
|
||||
def test_incompatible_versions(self, mock_driver_ver, mock_chrome_ver):
|
||||
"""Test that mismatched major versions are incompatible."""
|
||||
mock_chrome_ver.return_value = "126.0.6478.55"
|
||||
mock_driver_ver.return_value = "125"
|
||||
self.assertFalse(is_chromedriver_compatible("/usr/bin/chromedriver"))
|
||||
|
||||
@patch('sources.browser.chromedriver_autoinstaller.get_chrome_version')
|
||||
def test_no_chrome_version_assumes_compatible(self, mock_chrome_ver):
|
||||
"""Test that missing Chrome version defaults to compatible."""
|
||||
mock_chrome_ver.return_value = None
|
||||
self.assertTrue(is_chromedriver_compatible("/usr/bin/chromedriver"))
|
||||
|
||||
@patch('sources.browser.chromedriver_autoinstaller.get_chrome_version')
|
||||
@patch('sources.browser.get_chromedriver_version')
|
||||
def test_no_driver_version_assumes_compatible(self, mock_driver_ver, mock_chrome_ver):
|
||||
"""Test that missing driver version defaults to compatible."""
|
||||
mock_chrome_ver.return_value = "125.0.6422.78"
|
||||
mock_driver_ver.return_value = ""
|
||||
self.assertTrue(is_chromedriver_compatible("/usr/bin/chromedriver"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,92 @@
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
import shutil
|
||||
import logging
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from sources.logger import Logger
|
||||
|
||||
|
||||
class TestLogger(unittest.TestCase):
|
||||
"""Test suite for the Logger class."""
|
||||
|
||||
def setUp(self):
|
||||
self.logger = Logger("test_logger.log")
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists('.logs'):
|
||||
for handler in self.logger.logger.handlers[:]:
|
||||
handler.close()
|
||||
self.logger.logger.removeHandler(handler)
|
||||
log_path = os.path.join('.logs', 'test_logger.log')
|
||||
if os.path.exists(log_path):
|
||||
os.remove(log_path)
|
||||
|
||||
def test_initialization(self):
|
||||
"""Test logger initializes correctly."""
|
||||
self.assertTrue(self.logger.enabled)
|
||||
self.assertIsNotNone(self.logger.logger)
|
||||
self.assertTrue(os.path.exists('.logs'))
|
||||
|
||||
def test_log_creates_file(self):
|
||||
"""Test that logging creates a log file."""
|
||||
self.logger.info("test message")
|
||||
self.assertTrue(os.path.exists(self.logger.log_path))
|
||||
|
||||
def test_log_writes_message(self):
|
||||
"""Test that log messages are written to file."""
|
||||
self.logger.info("hello world")
|
||||
with open(self.logger.log_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.assertIn("hello world", content)
|
||||
|
||||
def test_log_deduplication(self):
|
||||
"""Test that consecutive identical messages are not duplicated."""
|
||||
self.logger.info("duplicate message")
|
||||
self.logger.info("duplicate message")
|
||||
with open(self.logger.log_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.assertEqual(content.count("duplicate message"), 1)
|
||||
|
||||
def test_log_different_messages(self):
|
||||
"""Test that different messages are all written."""
|
||||
self.logger.info("message one")
|
||||
self.logger.info("message two")
|
||||
with open(self.logger.log_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.assertIn("message one", content)
|
||||
self.assertIn("message two", content)
|
||||
|
||||
def test_error_level(self):
|
||||
"""Test error level logging."""
|
||||
self.logger.error("error occurred")
|
||||
with open(self.logger.log_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.assertIn("ERROR", content)
|
||||
self.assertIn("error occurred", content)
|
||||
|
||||
def test_warning_level(self):
|
||||
"""Test warning level logging."""
|
||||
self.logger.warning("warning issued")
|
||||
with open(self.logger.log_path, 'r') as f:
|
||||
content = f.read()
|
||||
self.assertIn("WARNING", content)
|
||||
self.assertIn("warning issued", content)
|
||||
|
||||
def test_create_folder(self):
|
||||
"""Test folder creation."""
|
||||
test_path = ".test_log_folder"
|
||||
result = self.logger.create_folder(test_path)
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(os.path.exists(test_path))
|
||||
os.rmdir(test_path)
|
||||
|
||||
def test_create_folder_already_exists(self):
|
||||
"""Test folder creation when folder already exists."""
|
||||
result = self.logger.create_folder('.logs')
|
||||
self.assertTrue(result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,232 @@
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
import os
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from sources.llm_provider import Provider
|
||||
|
||||
|
||||
class TestMiniMaxProvider(unittest.TestCase):
|
||||
"""Test cases for MiniMax provider integration."""
|
||||
|
||||
def test_minimax_provider_registered(self):
|
||||
"""Test that minimax provider is registered in available_providers."""
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
self.assertIn("minimax", provider.available_providers)
|
||||
|
||||
def test_minimax_in_unsafe_providers(self):
|
||||
"""Test that minimax is in unsafe_providers list."""
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
self.assertIn("minimax", provider.unsafe_providers)
|
||||
|
||||
def test_minimax_api_key_required(self):
|
||||
"""Test that API key is fetched for minimax provider."""
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-minimax-key') as mock_get_key:
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
mock_get_key.assert_called_with("minimax")
|
||||
self.assertEqual(provider.api_key, 'test-minimax-key')
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_local_not_supported(self, mock_openai_class):
|
||||
"""Test that minimax provider raises error when is_local=True."""
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=True)
|
||||
provider.api_key = 'test-key'
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
with self.assertRaises(Exception) as context:
|
||||
provider.minimax_fn(history)
|
||||
self.assertIn("not available for local use", str(context.exception))
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_uses_correct_base_url(self, mock_openai_class):
|
||||
"""Test that minimax provider uses correct base URL."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Hello!"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
mock_openai_class.assert_called_with(
|
||||
api_key='test-key',
|
||||
base_url='https://api.minimax.io/v1'
|
||||
)
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {
|
||||
'MINIMAX_API_KEY': 'test-key',
|
||||
'MINIMAX_BASE_URL': 'https://api.minimaxi.com/v1'
|
||||
})
|
||||
def test_minimax_custom_base_url(self, mock_openai_class):
|
||||
"""Test that minimax provider uses custom base URL from env."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Hello!"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
mock_openai_class.assert_called_with(
|
||||
api_key='test-key',
|
||||
base_url='https://api.minimaxi.com/v1'
|
||||
)
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_uses_temperature_one(self, mock_openai_class):
|
||||
"""Test that minimax provider uses temperature=1.0."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Hello!"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
call_kwargs = mock_client.chat.completions.create.call_args[1]
|
||||
self.assertEqual(call_kwargs['temperature'], 1.0)
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_returns_response_content(self, mock_openai_class):
|
||||
"""Test that minimax provider returns response content."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Test response"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
result = provider.minimax_fn(history)
|
||||
|
||||
self.assertEqual(result, "Test response")
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_handles_empty_response(self, mock_openai_class):
|
||||
"""Test that minimax provider handles empty response."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_client.chat.completions.create.return_value = None
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
provider.minimax_fn(history)
|
||||
self.assertIn("response is empty", str(context.exception))
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_handles_api_error(self, mock_openai_class):
|
||||
"""Test that minimax provider handles API errors."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_client.chat.completions.create.side_effect = Exception("API rate limit exceeded")
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
|
||||
with self.assertRaises(Exception) as context:
|
||||
provider.minimax_fn(history)
|
||||
self.assertIn("MiniMax API error", str(context.exception))
|
||||
|
||||
|
||||
class TestMiniMaxProviderModels(unittest.TestCase):
|
||||
"""Test cases for MiniMax provider model configurations."""
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_m27_model(self, mock_openai_class):
|
||||
"""Test MiniMax-M2.7 model."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Response"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.7", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
call_kwargs = mock_client.chat.completions.create.call_args[1]
|
||||
self.assertEqual(call_kwargs['model'], "MiniMax-M2.7")
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_m27_highspeed_model(self, mock_openai_class):
|
||||
"""Test MiniMax-M2.7-highspeed model."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Response"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.7-highspeed", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
call_kwargs = mock_client.chat.completions.create.call_args[1]
|
||||
self.assertEqual(call_kwargs['model'], "MiniMax-M2.7-highspeed")
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_m25_model(self, mock_openai_class):
|
||||
"""Test MiniMax-M2.5 model."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Response"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
call_kwargs = mock_client.chat.completions.create.call_args[1]
|
||||
self.assertEqual(call_kwargs['model'], "MiniMax-M2.5")
|
||||
|
||||
@patch('sources.llm_provider.OpenAI')
|
||||
@patch.dict(os.environ, {'MINIMAX_API_KEY': 'test-key'})
|
||||
def test_minimax_m25_highspeed_model(self, mock_openai_class):
|
||||
"""Test MiniMax-M2.5-highspeed model."""
|
||||
mock_client = MagicMock()
|
||||
mock_openai_class.return_value = mock_client
|
||||
mock_response = MagicMock()
|
||||
mock_response.choices = [MagicMock(message=MagicMock(content="Response"))]
|
||||
mock_client.chat.completions.create.return_value = mock_response
|
||||
|
||||
with patch.object(Provider, 'get_api_key', return_value='test-key'):
|
||||
provider = Provider("minimax", "MiniMax-M2.5-highspeed", is_local=False)
|
||||
history = [{"role": "user", "content": "Hello"}]
|
||||
provider.minimax_fn(history)
|
||||
|
||||
call_kwargs = mock_client.chat.completions.create.call_args[1]
|
||||
self.assertEqual(call_kwargs['model'], "MiniMax-M2.5-highspeed")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,108 @@
|
||||
import unittest
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
# Mock heavy dependencies to allow import without installing them all
|
||||
for mod_name in [
|
||||
'torch', 'transformers', 'kokoro', 'adaptive_classifier', 'text2emotion',
|
||||
'ollama', 'openai', 'together', 'IPython', 'IPython.display',
|
||||
'playsound3', 'soundfile', 'pyaudio', 'librosa',
|
||||
'pypdf', 'langid', 'pypinyin', 'fake_useragent',
|
||||
'chromedriver_autoinstaller', 'num2words', 'sentencepiece', 'sacremoses',
|
||||
'scipy', 'numpy', 'selenium_stealth', 'undetected_chromedriver',
|
||||
'markdownify',
|
||||
]:
|
||||
if mod_name not in sys.modules:
|
||||
sys.modules[mod_name] = MagicMock()
|
||||
|
||||
os.environ.setdefault('WORK_DIR', '/tmp')
|
||||
|
||||
from sources.agents.planner_agent import PlannerAgent
|
||||
|
||||
|
||||
class TestPlannerAgentParsing(unittest.TestCase):
|
||||
"""Test suite for PlannerAgent.parse_agent_tasks JSON parsing robustness."""
|
||||
|
||||
def setUp(self):
|
||||
self.agent = PlannerAgent.__new__(PlannerAgent)
|
||||
self.agent.tools = {"json": MagicMock()}
|
||||
self.agent.tools["json"].tag = "json"
|
||||
self.agent.logger = MagicMock()
|
||||
self.agent.agents = {
|
||||
"coder": MagicMock(),
|
||||
"file": MagicMock(),
|
||||
"web": MagicMock(),
|
||||
"casual": MagicMock()
|
||||
}
|
||||
|
||||
def test_parse_valid_json(self):
|
||||
"""Test that valid JSON plan is parsed correctly."""
|
||||
valid_json = '{"plan": [{"agent": "web", "id": "1", "task": "Search info", "need": []}]}'
|
||||
self.agent.tools["json"].load_exec_block.return_value = ([valid_json], None)
|
||||
|
||||
with patch.object(self.agent, 'get_task_names', return_value=["Task 1: Search info"]):
|
||||
result = self.agent.parse_agent_tasks("dummy text")
|
||||
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(result[0][1]['agent'], 'web')
|
||||
|
||||
def test_parse_malformed_json_returns_empty(self):
|
||||
"""Test that malformed JSON returns empty list instead of crashing."""
|
||||
malformed_json = '{"plan": [{"agent": "web", "id": "1" "task": "missing comma"}]}'
|
||||
self.agent.tools["json"].load_exec_block.return_value = ([malformed_json], None)
|
||||
|
||||
with patch.object(self.agent, 'get_task_names', return_value=[]):
|
||||
result = self.agent.parse_agent_tasks("dummy text")
|
||||
|
||||
self.assertEqual(result, [])
|
||||
self.agent.logger.warning.assert_called_once()
|
||||
|
||||
def test_parse_truncated_json_returns_empty(self):
|
||||
"""Test that truncated JSON returns empty list instead of crashing."""
|
||||
truncated_json = '{"plan": [{"agent": "web", "id": "1", "task": "Search'
|
||||
self.agent.tools["json"].load_exec_block.return_value = ([truncated_json], None)
|
||||
|
||||
with patch.object(self.agent, 'get_task_names', return_value=[]):
|
||||
result = self.agent.parse_agent_tasks("dummy text")
|
||||
|
||||
self.assertEqual(result, [])
|
||||
self.agent.logger.warning.assert_called_once()
|
||||
|
||||
def test_parse_no_blocks_returns_empty(self):
|
||||
"""Test that missing blocks returns empty list."""
|
||||
self.agent.tools["json"].load_exec_block.return_value = (None, None)
|
||||
|
||||
with patch.object(self.agent, 'get_task_names', return_value=[]):
|
||||
result = self.agent.parse_agent_tasks("no json here")
|
||||
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_parse_invalid_agent_returns_empty(self):
|
||||
"""Test that an unknown agent name returns empty list."""
|
||||
valid_json = '{"plan": [{"agent": "unknown_agent", "id": "1", "task": "Do something", "need": []}]}'
|
||||
self.agent.tools["json"].load_exec_block.return_value = ([valid_json], None)
|
||||
|
||||
with patch.object(self.agent, 'get_task_names', return_value=["Task 1"]):
|
||||
result = self.agent.parse_agent_tasks("dummy text")
|
||||
|
||||
self.assertEqual(result, [])
|
||||
|
||||
def test_parse_multiple_tasks(self):
|
||||
"""Test parsing a plan with multiple tasks."""
|
||||
multi_task_json = '{"plan": [{"agent": "web", "id": "1", "task": "Search", "need": []}, {"agent": "coder", "id": "2", "task": "Code it", "need": ["1"]}]}'
|
||||
self.agent.tools["json"].load_exec_block.return_value = ([multi_task_json], None)
|
||||
|
||||
with patch.object(self.agent, 'get_task_names', return_value=["Task 1: Search", "Task 2: Code it"]):
|
||||
result = self.agent.parse_agent_tasks("dummy text")
|
||||
|
||||
self.assertEqual(len(result), 2)
|
||||
self.assertEqual(result[0][1]['agent'], 'web')
|
||||
self.assertEqual(result[1][1]['agent'], 'coder')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -0,0 +1,42 @@
|
||||
import unittest
|
||||
import os
|
||||
import sys
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
|
||||
from sources.utility import get_color_map
|
||||
|
||||
|
||||
class TestUtility(unittest.TestCase):
|
||||
"""Test suite for utility module functions."""
|
||||
|
||||
def test_get_color_map_returns_dict(self):
|
||||
"""Test that get_color_map returns a dictionary."""
|
||||
color_map = get_color_map()
|
||||
self.assertIsInstance(color_map, dict)
|
||||
|
||||
def test_get_color_map_has_required_keys(self):
|
||||
"""Test that color map contains all required color keys."""
|
||||
color_map = get_color_map()
|
||||
required_keys = ["success", "failure", "status", "code", "warning", "output", "info"]
|
||||
for key in required_keys:
|
||||
self.assertIn(key, color_map, f"Missing key: {key}")
|
||||
|
||||
def test_get_color_map_values_are_strings(self):
|
||||
"""Test that all color values are strings."""
|
||||
color_map = get_color_map()
|
||||
for key, value in color_map.items():
|
||||
self.assertIsInstance(value, str, f"Value for '{key}' should be a string")
|
||||
|
||||
def test_success_is_green(self):
|
||||
"""Test that success maps to green."""
|
||||
color_map = get_color_map()
|
||||
self.assertEqual(color_map["success"], "green")
|
||||
|
||||
def test_failure_is_red(self):
|
||||
"""Test that failure maps to red."""
|
||||
color_map = get_color_map()
|
||||
self.assertEqual(color_map["failure"], "red")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user