# Spec : Ajouter un système d'agent (web search + PDF) au panel Ollama

## Contexte
Mon panel web parle déjà à Ollama (endpoint `/api/chat`, `/api/tags`, etc.) et permet de chatter avec les modèles installés. Je veux étendre le panel pour qu'il devienne un véritable agent : capable de chercher sur le web, lire des pages, et générer des PDF à la demande de l'utilisateur, via le mécanisme de **tool calling** d'Ollama.

## Objectifs

1. **Détection automatique** des modèles Ollama qui supportent le tool calling
2. **Trois outils** disponibles : recherche web (SearXNG), lecture de page, génération de PDF
3. **Boucle agentique** : le modèle peut enchaîner plusieurs outils avant de répondre
4. **UX** : afficher les appels d'outils en temps réel dans le chat

## Infrastructure existante

- **Ollama** : déjà installé et accessible
- **SearXNG** : tourne sur `http://localhost:8888` (à adapter), avec `formats: [html, json]` activé dans `settings.yml`. L'API JSON est sur `GET /search?q=...&format=json`
- **Panel web** : à toi de me dire le stack actuel et d'intégrer sans casser

## 1. Détection des modèles compatibles tools

Quand le panel liste les modèles via `GET /api/tags`, ajoute pour chacun un appel à `POST /api/show` avec `{"name": "<nom_modele>"}` et inspecte la réponse :

```json
{
  "capabilities": ["completion", "tools", "insert"],
  ...
}
```

Si `"tools"` est présent dans `capabilities`, le modèle est compatible.

**Fallback** (si la version d'Ollama est trop ancienne et ne renvoie pas `capabilities`) :
Whitelist sur le préfixe du nom du modèle. Familles connues compatibles :
- `qwen2.5`, `qwen2.5-coder`, `qwen3`
- `llama3.1`, `llama3.2`, `llama3.3`
- `mistral-nemo`, `mistral-small`, `mistral-large`
- `command-r`, `command-r-plus`
- `hermes3`
- `firefunction-v2`

**UI** : dans le sélecteur de modèles, affiche un petit badge "🛠 tools" à côté des modèles compatibles. Désactive (ou cache, à toi de voir) les outils dans l'interface quand le modèle sélectionné n'est pas compatible.

## 2. Les trois outils

### 2.1 `rechercher_web(requete, nb_resultats=5)`

Appelle SearXNG :
```
GET http://localhost:8888/search?q={requete}&format=json
```

Récupère le tableau `results` de la réponse, prends les `nb_resultats` premiers, et formate-les en texte lisible :
```
[1] Titre du résultat
URL: https://...
Snippet : description courte
```

Retourne ce texte au modèle. En cas d'erreur (réseau, 403, etc.), retourne un message d'erreur clair.

### 2.2 `lire_page(url)`

Fait un `GET` sur l'URL avec un User-Agent crédible (genre `Mozilla/5.0 (compatible; OllamaAgent/1.0)`) et un timeout de 10s.

Extrait le texte principal :
- **Préféré** : `trafilatura` (Python) ou `@mozilla/readability` (Node) — beaucoup plus propre
- **Fallback** : BeautifulSoup ou Cheerio, vire `script`, `style`, `nav`, `footer`, `header`, `aside`, puis `get_text()`

**Tronque à 8000 caractères** pour ne pas saturer le contexte du modèle. Ajoute `...` à la fin si tronqué.

**Sécurité importante** : bloque les requêtes vers les IP privées (`127.0.0.0/8`, `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `169.254.0.0/16`, `::1`, `fc00::/7`) pour éviter le SSRF, sauf si tu détermines que c'est sans risque dans mon archi. Si je veux pouvoir lire un SearXNG ou autre service interne, on whitelistera explicitement plus tard.

### 2.3 `creer_pdf(titre, contenu_markdown)`

1. Convertit `contenu_markdown` → HTML avec ces extensions activées : `tables`, `fenced_code`, `nl2br`
2. Enveloppe dans un template HTML avec CSS soigné (voir ci-dessous)
3. Rend en PDF avec **WeasyPrint** (Python) ou équivalent
4. Sauvegarde dans un dossier servable statiquement par le panel (genre `static/pdfs/` ou volume Docker monté)
5. Nommage : `{timestamp}_{titre_safe}.pdf` (titre nettoyé : alphanumerique + tirets, max 50 char)
6. **Retourne au modèle** : un message court genre `"PDF généré avec succès. URL : /pdfs/...pdf"` — pas le chemin disque, l'URL servable
7. **Dans l'UI** : affiche un bouton/lien "📄 Télécharger le PDF" qui pointe vers cette URL

CSS recommandé pour le PDF :

```css
@page { margin: 2cm; }
body { font-family: -apple-system, "Segoe UI", sans-serif; line-height: 1.6; color: #222; }
h1 { color: #1a1a2e; border-bottom: 2px solid #1a1a2e; padding-bottom: 6px; }
h2 { color: #333; margin-top: 1.5em; }
h3 { color: #444; }
code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-family: ui-monospace, monospace; }
pre { background: #f4f4f4; padding: 1em; border-radius: 4px; overflow-x: auto; }
table { border-collapse: collapse; width: 100%; margin: 1em 0; }
td, th { border: 1px solid #ccc; padding: 8px; text-align: left; }
th { background: #f0f0f0; }
blockquote { border-left: 4px solid #ccc; margin-left: 0; padding-left: 1em; color: #555; font-style: italic; }
a { color: #1a5dc7; text-decoration: none; }
img { max-width: 100%; }
```

**Dépendances système pour WeasyPrint** (si Debian/Ubuntu) :
```bash
apt install libpango-1.0-0 libpangoft2-1.0-0
pip install weasyprint markdown
```

## 3. Boucle agentique

Quand l'utilisateur envoie un message :

1. Si le modèle sélectionné supporte les tools, ajouter le paramètre `tools` à la requête `/api/chat`
2. Schéma JSON des tools à envoyer à Ollama :

```json
{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "rechercher_web",
        "description": "Recherche sur le web via SearXNG. À utiliser pour toute info récente, actualités, faits à vérifier, prix, scores, données qui changent.",
        "parameters": {
          "type": "object",
          "properties": {
            "requete": {"type": "string", "description": "Requête de recherche, courte et précise"},
            "nb_resultats": {"type": "integer", "description": "Nombre de résultats (défaut 5)"}
          },
          "required": ["requete"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "lire_page",
        "description": "Récupère le contenu texte d'une page web. À utiliser après une recherche pour approfondir un résultat.",
        "parameters": {
          "type": "object",
          "properties": {
            "url": {"type": "string", "description": "URL complète à lire"}
          },
          "required": ["url"]
        }
      }
    },
    {
      "type": "function",
      "function": {
        "name": "creer_pdf",
        "description": "Génère un fichier PDF. À utiliser quand l'utilisateur demande un document, rapport, PDF, ou export.",
        "parameters": {
          "type": "object",
          "properties": {
            "titre": {"type": "string", "description": "Titre du document"},
            "contenu_markdown": {"type": "string", "description": "Contenu complet en markdown (avec ##, listes, tableaux, etc.)"}
          },
          "required": ["titre", "contenu_markdown"]
        }
      }
    }
  ]
}
```

3. **Boucle** :
   - Appeler `/api/chat`
   - Si la réponse contient `message.tool_calls` non vide :
     - Pour chaque tool call, exécuter la fonction correspondante
     - Ajouter le résultat au messages history avec `{"role": "tool", "content": "<résultat>"}`
     - Rappeler `/api/chat` avec l'historique complet
   - Sinon : afficher la réponse finale et sortir
   - **Limite à 10 itérations** pour éviter les boucles infinies (afficher un avertissement si atteint)

4. **Système prompt à injecter** quand les tools sont actifs (en plus du prompt utilisateur si existant) :

```
Tu es un assistant avec accès à 3 outils : rechercher_web, lire_page, et creer_pdf.

Règles :
- Utilise rechercher_web pour toute info récente ou à vérifier
- Utilise lire_page après une recherche pour approfondir 1 ou 2 résultats pertinents (pas tous)
- Utilise creer_pdf quand l'utilisateur demande un document, rapport, ou PDF
- Quand tu génères un PDF basé sur des sources web, cite les URLs dans le PDF
- Sois efficace : pas plus de 2-3 recherches sauf si vraiment nécessaire
- Réponds en français si l'utilisateur écrit en français
```

## 4. UX dans le chat

Pendant la boucle agentique, afficher dans l'UI les étapes en temps réel :

- `🔍 Recherche web : "actualités IA"`
- `📖 Lecture de https://...`
- `📄 Génération du PDF "Rapport sur l'IA"...`
- Puis la réponse finale du modèle

Ces lignes peuvent apparaître dans un encart "thinking/tools" repliable au-dessus de la réponse, ou inline avant elle. À toi de voir ce qui colle au design existant.

## 5. Streaming (si déjà implémenté)

Si le panel utilise le streaming SSE pour afficher les réponses progressives :

- Pour la passe finale (après tous les tools), streamer normalement
- Pour les passes intermédiaires (qui se terminent par des tool_calls), **on ne peut pas streamer** car Ollama renvoie les tool_calls à la fin. Donc : non-stream pendant les passes outils, stream pour la réponse finale uniquement.

Pratiquement : `stream: false` tant qu'on est dans la boucle d'outils, `stream: true` quand on détecte que le modèle n'appelle plus d'outils.

## 6. Configuration

Ajoute un panneau de config (dans les settings du panel) avec :

- URL de Ollama (déjà existant)
- URL de SearXNG (default : `http://localhost:8888`)
- Toggle "Activer les outils" (par utilisateur ou global)
- Toggle individuel par outil (au cas où je veux désactiver le PDF mais garder le web)
- Limite max d'itérations (default 10)

## Stack

Dis-moi le stack actuel du panel (langage, framework, comment tu gères les routes API et le front) et adapte. Si Python/FastAPI ou Node/Express c'est le plus naturel pour intégrer ces outils.

## Tests à faire après implémentation

1. **Détection** : avec `qwen2.5:14b` installé → doit apparaître avec badge tools. Avec un vieux modèle non compatible → pas de badge.
2. **Recherche** : "Quelles sont les actualités IA d'aujourd'hui ?" → doit déclencher `rechercher_web`, afficher l'étape, puis répondre.
3. **PDF simple** : "Fais-moi un PDF sur la photosynthèse" → doit générer un PDF cohérent et fournir le lien.
4. **Chaîne complète** : "Fais-moi un rapport PDF sur les dernières news IA" → doit faire recherche → lecture → PDF avec sources.
5. **Limite d'itérations** : forcer une boucle infinie (avec un prompt vicieux) doit s'arrêter à 10 et afficher un avertissement.

## Optionnel (phase 2, pas nécessaire maintenant)

- Outil `generer_image(prompt)` qui appelle Stable Diffusion / ComfyUI et insère l'image dans le PDF
- Outil `executer_python(code)` dans un sandbox (Docker, e2b, ou wasi)
- Historique des PDF générés, page dédiée pour les retrouver
- Streaming des étapes via SSE pendant la boucle d'outils