Ajout Effi-OCR (Tesseract) + Web Scraper pour OpenWebUI
- Effi-OCR: Service OCR avec Tesseract (multilingue FR/EN) * API FastAPI sur port 8300 * Prétraitement images (contraste, niveaux de gris) * Tool OpenWebUI intégré - Web Scraper: Service Python (BeautifulSoup) sur port 11235 * Alternative à Crawl4AI (incompatible ARM64/J5040) * Scraping HTML basique mais fonctionnel * Intégré au docker-compose d'effi-server - Documentation: * ROADMAP-FONCTIONNALITES.md: Plan des 9 fonctionnalités * WEB-SCRAPER-CONFIG.md: Configuration OpenWebUI * WEB-SCRAPER-TOOL.md: Code du Tool - Script start-all-services.sh mis à jour (6 services)
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# Configuration Web Search and Crawl (OpenWebUI)
|
||||
|
||||
Guide de configuration du Tool **Web Search and Crawl** dans OpenWebUI avec Crawl4AI.
|
||||
|
||||
## Prérequis
|
||||
|
||||
- [ ] Crawl4AI installé et démarré sur effi-server (voir `install-on-effiserver.sh`)
|
||||
- [ ] OpenWebUI accessible sur http://192.168.1.27:3000
|
||||
|
||||
## Étapes de configuration
|
||||
|
||||
### 1. Vérifier que Crawl4AI tourne
|
||||
|
||||
Sur **effi-server** :
|
||||
```bash
|
||||
curl http://localhost:11235/health
|
||||
# Doit retourner : {"status":"healthy"}
|
||||
```
|
||||
|
||||
### 2. Accéder au Tool dans OpenWebUI
|
||||
|
||||
1. Ouvrir http://192.168.1.27:3000
|
||||
2. Se connecter en admin
|
||||
3. Aller dans **Admin Panel** → **Tools**
|
||||
4. Trouver **Web Search and Crawl**
|
||||
5. Cliquer sur l'icône ⚙️ (Settings/Valves)
|
||||
|
||||
### 3. Configurer les Valves
|
||||
|
||||
| Valve | Valeur | Description |
|
||||
|-------|--------|-------------|
|
||||
| **Crawl4AI Base URL** | `http://localhost:11235` | URL du service Crawl4AI |
|
||||
| **Crawl4AI API Token** | (vide) | Token si configuré dans docker-compose |
|
||||
| **Timeout** | `30000` | Timeout en millisecondes (30s) |
|
||||
| **Max Length** | `10000` | Nombre max de caractères extraits |
|
||||
| **Enable JavaScript** | ✅ | Activer l'exécution JavaScript |
|
||||
|
||||
### 4. Activer le Tool
|
||||
|
||||
1. Cocher la case **Enable** sur le Tool
|
||||
2. Cliquer sur **Save**
|
||||
3. Le Tool est maintenant actif !
|
||||
|
||||
## Test de fonctionnement
|
||||
|
||||
Créer une nouvelle conversation et demander :
|
||||
|
||||
```
|
||||
"Effi, cherche les dernières nouvelles sur Fedora 42 et résume-moi les articles"
|
||||
```
|
||||
|
||||
L'agent devrait :
|
||||
1. Utiliser la recherche web
|
||||
2. Scraper les pages trouvées
|
||||
3. Te présenter un résumé
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Crawl4AI not responding"
|
||||
|
||||
Vérifier sur effi-server :
|
||||
```bash
|
||||
docker ps | grep crawl4ai
|
||||
docker logs effi-crawl4ai
|
||||
```
|
||||
|
||||
### "Timeout error"
|
||||
|
||||
Augmenter la valve **Timeout** à `60000` (60 secondes) dans les settings du Tool.
|
||||
|
||||
### "Memory error"
|
||||
|
||||
La version CPU est limitée à 2GB. Pour les pages très lourdes :
|
||||
- Augmenter dans `docker-compose.yml` : `memory: 3G`
|
||||
- Redémarrer : `docker-compose up -d`
|
||||
|
||||
## Commandes utiles
|
||||
|
||||
**Redémarrer Crawl4AI** :
|
||||
```bash
|
||||
cd ~/effi-crawl4ai
|
||||
docker-compose restart
|
||||
```
|
||||
|
||||
**Voir les logs** :
|
||||
```bash
|
||||
docker logs -f effi-crawl4ai
|
||||
```
|
||||
|
||||
**Mettre à jour l'image** :
|
||||
```bash
|
||||
cd ~/effi-crawl4ai
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## Fonctionnalités disponibles
|
||||
|
||||
Une fois configuré, tu peux demander à Effi :
|
||||
|
||||
| Requête | Description |
|
||||
|---------|-------------|
|
||||
| "Cherche X et résume" | Recherche + résumé automatique |
|
||||
| "Extraire le contenu de [URL]" | Scraping direct d'une page |
|
||||
| "Comparer [produit] sur plusieurs sites" | Multi-site scraping |
|
||||
| "Vérifier les prix de [article]" | Surveillance de prix |
|
||||
| "Trouver des documentations sur [sujet]" | Recherche technique |
|
||||
|
||||
---
|
||||
|
||||
*Configuration créée le 6 Avril 2026*
|
||||
@@ -0,0 +1,188 @@
|
||||
# Tool Web Scraping pour OpenWebUI
|
||||
|
||||
## Configuration
|
||||
|
||||
Dans OpenWebUI (http://192.168.1.27:3000) :
|
||||
|
||||
1. **Aller dans** : Admin → Tools → Create Tool
|
||||
2. **Nom** : Web Scraper
|
||||
3. **ID** : web_scraper
|
||||
4. **Description** : Extrait le contenu texte de n'importe quelle URL web
|
||||
|
||||
## Code du Tool
|
||||
|
||||
```python
|
||||
from typing import Optional
|
||||
import requests
|
||||
from urllib.parse import urlparse
|
||||
import re
|
||||
|
||||
class Tools:
|
||||
def __init__(self):
|
||||
"""Initialize Web Scraper Tool"""
|
||||
self.timeout = 15
|
||||
self.headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.0'
|
||||
}
|
||||
|
||||
def scrape_url(self, url: str, max_chars: int = 5000) -> str:
|
||||
"""
|
||||
Scrape le contenu texte d'une URL web
|
||||
|
||||
Args:
|
||||
url: L'URL à scraper (doit commencer par http:// ou https://)
|
||||
max_chars: Nombre maximum de caractères à retourner (défaut: 5000)
|
||||
|
||||
Returns:
|
||||
Le contenu texte extrait de la page
|
||||
"""
|
||||
try:
|
||||
# Validation URL
|
||||
if not url.startswith(('http://', 'https://')):
|
||||
return "Erreur: L'URL doit commencer par http:// ou https://"
|
||||
|
||||
parsed = urlparse(url)
|
||||
if not parsed.netloc:
|
||||
return "Erreur: URL invalide"
|
||||
|
||||
# Télécharger la page
|
||||
response = requests.get(url, headers=self.headers, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
# Détecter l'encodage
|
||||
if response.encoding == 'ISO-8859-1':
|
||||
response.encoding = response.apparent_encoding
|
||||
|
||||
html = response.text
|
||||
|
||||
# Extraction simple du texte (sans BeautifulSoup pour garder le Tool léger)
|
||||
# Supprimer les scripts et styles
|
||||
text = re.sub(r'<script[^>]*>[^<]*</script>', '', html, flags=re.IGNORECASE | re.DOTALL)
|
||||
text = re.sub(r'<style[^>]*>[^<]*</style>', '', text, flags=re.IGNORECASE | re.DOTALL)
|
||||
|
||||
# Supprimer les balises HTML
|
||||
text = re.sub(r'<[^>]+>', ' ', text)
|
||||
|
||||
# Nettoyer le texte
|
||||
text = re.sub(r'\s+', ' ', text) # Espaces multiples
|
||||
text = text.strip()
|
||||
|
||||
# Décoder les entités HTML
|
||||
text = text.replace('&', '&')
|
||||
text = text.replace('<', '<')
|
||||
text = text.replace('>', '>')
|
||||
text = text.replace('"', '"')
|
||||
text = text.replace(''', "'")
|
||||
text = text.replace(' ', ' ')
|
||||
|
||||
# Tronquer si nécessaire
|
||||
if len(text) > max_chars:
|
||||
text = text[:max_chars] + f"\n\n[...] {len(text) - max_chars} caractères supplémentaires"
|
||||
|
||||
return f"Contenu extrait de {url}:\n\n{text}"
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return f"Erreur: Timeout après {self.timeout}s pour {url}"
|
||||
except requests.exceptions.ConnectionError:
|
||||
return f"Erreur: Impossible de se connecter à {url}"
|
||||
except requests.exceptions.HTTPError as e:
|
||||
return f"Erreur HTTP {e.response.status_code}: {url}"
|
||||
except Exception as e:
|
||||
return f"Erreur lors du scraping: {str(e)}"
|
||||
|
||||
def scrape_article(self, url: str) -> str:
|
||||
"""
|
||||
Scrape un article en essayant d'extraire le contenu principal uniquement
|
||||
|
||||
Args:
|
||||
url: L'URL de l'article
|
||||
|
||||
Returns:
|
||||
Le contenu principal de l'article (titre + texte)
|
||||
"""
|
||||
try:
|
||||
if not url.startswith(('http://', 'https://')):
|
||||
return "Erreur: L'URL doit commencer par http:// ou https://"
|
||||
|
||||
response = requests.get(url, headers=self.headers, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
|
||||
if response.encoding == 'ISO-8859-1':
|
||||
response.encoding = response.apparent_encoding
|
||||
|
||||
html = response.text
|
||||
|
||||
# Extraire le titre
|
||||
title_match = re.search(r'<title[^>]*>([^<]*)</title>', html, re.IGNORECASE)
|
||||
title = title_match.group(1).strip() if title_match else "Titre non trouvé"
|
||||
|
||||
# Essayer d'extraire le contenu article (heuristique simple)
|
||||
# Chercher les balises article, main, ou div avec classe contenant "content"
|
||||
article_patterns = [
|
||||
r'<article[^>]*>(.*?)</article>',
|
||||
r'<main[^>]*>(.*?)</main>',
|
||||
r'<div[^>]*class=["\'][^"\']*(?:content|article|post)[^"\']*["\'][^>]*>(.*?)</div>',
|
||||
]
|
||||
|
||||
content = None
|
||||
for pattern in article_patterns:
|
||||
match = re.search(pattern, html, re.IGNORECASE | re.DOTALL)
|
||||
if match:
|
||||
content = match.group(1)
|
||||
break
|
||||
|
||||
# Si pas trouvé, prendre tout le body
|
||||
if not content:
|
||||
body_match = re.search(r'<body[^>]*>(.*?)</body>', html, re.IGNORECASE | re.DOTALL)
|
||||
content = body_match.group(1) if body_match else html
|
||||
|
||||
# Nettoyer le contenu
|
||||
content = re.sub(r'<script[^>]*>[^<]*</script>', '', content, flags=re.IGNORECASE | re.DOTALL)
|
||||
content = re.sub(r'<style[^>]*>[^<]*</style>', '', content, flags=re.IGNORECASE | re.DOTALL)
|
||||
content = re.sub(r'<[^>]+>', ' ', content)
|
||||
content = re.sub(r'\s+', ' ', content).strip()
|
||||
|
||||
# Décoder les entités HTML
|
||||
content = content.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
content = content.replace('"', '"').replace(''', "'")
|
||||
|
||||
# Limiter la taille
|
||||
if len(content) > 8000:
|
||||
content = content[:8000] + "\n\n[...] Contenu tronqué"
|
||||
|
||||
return f"Titre: {title}\nURL: {url}\n\n{content}"
|
||||
|
||||
except Exception as e:
|
||||
return f"Erreur: {str(e)}"
|
||||
```
|
||||
|
||||
## Utilisation
|
||||
|
||||
Une fois activé, les agents peuvent utiliser :
|
||||
|
||||
- `scrape_url("https://example.com/article")` - Extraire tout le texte d'une page
|
||||
- `scrape_article("https://example.com/article")` - Extraire le contenu article uniquement
|
||||
|
||||
## Exemples de prompts
|
||||
|
||||
```
|
||||
"Effi, résume-moi cet article : https://..."
|
||||
→ L'agent utilise scrape_article automatiquement
|
||||
|
||||
"Qu'est-ce qui est écrit sur cette page ? https://..."
|
||||
→ L'agent utilise scrape_url
|
||||
|
||||
"Cherche des infos sur X et lis les 3 premiers liens"
|
||||
→ Web search + scrape_url pour chaque résultat
|
||||
```
|
||||
|
||||
## Limitations
|
||||
|
||||
- Extraction basique (pas de JavaScript)
|
||||
- Timeout 15 secondes
|
||||
- Sites avec protection anti-bot peuvent bloquer
|
||||
- Ne fonctionne pas avec les paywalls
|
||||
|
||||
---
|
||||
|
||||
*Tool créé le 6 Avril 2026*
|
||||
Reference in New Issue
Block a user