9ca31e45e0
- Fix circular dependencies in agent/tools - Migrate from custom JSON to OpenAI tool calls format - Add async streaming (step_stream, complete_stream) - Simplify prompt system and remove token counting - Add 5 new API endpoints (/health, /v1/models, /api/memory/*) - Add 3 new tools (get_torrent_by_index, add_torrent_by_index, set_language) - Fix all 500 tests and add coverage config (80% threshold) - Add comprehensive docs (README, pytest guide) BREAKING: LLM interface changed, memory injection via get_memory()
517 lines
15 KiB
Markdown
517 lines
15 KiB
Markdown
# Changelog
|
|
|
|
## [Non publié] - 2024-01-XX
|
|
|
|
### 🎯 Objectif principal
|
|
Correction massive des dépendances circulaires et refactoring complet du système pour utiliser les tool calls natifs OpenAI. Migration de l'architecture vers un système plus propre et maintenable.
|
|
|
|
---
|
|
|
|
## 🔧 Corrections majeures
|
|
|
|
### 1. Agent Core (`agent/agent.py`)
|
|
**Refactoring complet du système d'agent**
|
|
|
|
- **Suppression du système JSON custom** :
|
|
- Retiré `_parse_intent()` qui parsait du JSON custom
|
|
- Retiré `_execute_action()` remplacé par `_execute_tool_call()`
|
|
- Migration vers les tool calls natifs OpenAI
|
|
|
|
- **Nouvelle interface LLM** :
|
|
- Ajout du `Protocol` `LLMClient` pour typage fort
|
|
- `complete()` retourne `Dict[str, Any]` (message avec tool_calls)
|
|
- `complete_stream()` retourne `AsyncGenerator` pour streaming
|
|
- Suppression du tuple `(response, usage)` - plus de comptage de tokens
|
|
|
|
- **Gestion des tool calls** :
|
|
- `_execute_tool_call()` parse les tool calls OpenAI
|
|
- Gestion des `tool_call_id` pour la conversation
|
|
- Boucle d'itération jusqu'à réponse finale ou max iterations
|
|
- Raise `MaxIterationsReachedError` si dépassement
|
|
|
|
- **Streaming asynchrone** :
|
|
- `step_stream()` pour réponses streamées
|
|
- Détection des tool calls avant streaming
|
|
- Fallback non-streaming si tool calls nécessaires
|
|
- Sauvegarde de la réponse complète en mémoire
|
|
|
|
- **Gestion de la mémoire** :
|
|
- Utilisation de `get_memory()` au lieu de passer `memory` partout
|
|
- `_prepare_messages()` pour construire le contexte
|
|
- Sauvegarde automatique après chaque step
|
|
- Ajout des messages user/assistant dans l'historique
|
|
|
|
### 2. LLM Clients
|
|
|
|
#### `agent/llm/deepseek.py`
|
|
- **Nouvelle signature** : `complete(messages, tools=None) -> Dict[str, Any]`
|
|
- **Streaming** : `complete_stream()` avec `httpx.AsyncClient`
|
|
- **Support des tool calls** : Ajout de `tools` et `tool_choice` dans le payload
|
|
- **Retour simplifié** : Retourne directement le message, pas de tuple
|
|
- **Gestion d'erreurs** : Raise `LLMAPIError` pour toutes les erreurs
|
|
|
|
#### `agent/llm/ollama.py`
|
|
- Même refactoring que DeepSeek
|
|
- Support des tool calls (si Ollama le supporte)
|
|
- Streaming avec `httpx.AsyncClient`
|
|
|
|
#### `agent/llm/exceptions.py` (NOUVEAU)
|
|
- `LLMError` - Exception de base
|
|
- `LLMConfigurationError` - Configuration invalide
|
|
- `LLMAPIError` - Erreur API
|
|
|
|
### 3. Prompts (`agent/prompts.py`)
|
|
|
|
**Simplification massive du système de prompts**
|
|
|
|
- **Suppression du prompt verbeux** :
|
|
- Plus de JSON context énorme
|
|
- Plus de liste exhaustive des outils
|
|
- Plus d'exemples JSON
|
|
|
|
- **Nouveau prompt court** :
|
|
```
|
|
You are a helpful AI assistant for managing a media library.
|
|
Your first task is to determine the user's language...
|
|
```
|
|
|
|
- **Contexte structuré** :
|
|
- `_format_episodic_context()` : Dernières recherches, downloads, erreurs
|
|
- `_format_stm_context()` : Topic actuel, langue de conversation
|
|
- Affichage limité (5 résultats, 3 downloads, 3 erreurs)
|
|
|
|
- **Tool specs OpenAI** :
|
|
- `build_tools_spec()` génère le format OpenAI
|
|
- Les tools sont passés via l'API, pas dans le prompt
|
|
|
|
### 4. Registry (`agent/registry.py`)
|
|
|
|
**Correction des dépendances circulaires**
|
|
|
|
- **Nouveau système d'enregistrement** :
|
|
- Décorateur `@tool` pour auto-enregistrement
|
|
- Liste globale `_tools` pour stocker les tools
|
|
- `make_tools()` appelle explicitement chaque fonction
|
|
|
|
- **Suppression des imports directs** :
|
|
- Plus d'imports dans `agent/tools/__init__.py`
|
|
- Imports dans `registry.py` au moment de l'enregistrement
|
|
- Évite les boucles d'imports
|
|
|
|
- **Génération automatique des schemas** :
|
|
- Inspection des signatures avec `inspect`
|
|
- Génération des `parameters` JSON Schema
|
|
- Extraction de la description depuis la docstring
|
|
|
|
### 5. Tools
|
|
|
|
#### `agent/tools/__init__.py`
|
|
- **Vidé complètement** pour éviter les imports circulaires
|
|
- Juste `__all__` pour la documentation
|
|
|
|
#### `agent/tools/api.py`
|
|
**Refactoring complet avec gestion de la mémoire**
|
|
|
|
- **`find_media_imdb_id()`** :
|
|
- Stocke le résultat dans `memory.stm.set_entity("last_media_search")`
|
|
- Set topic à "searching_media"
|
|
- Logging des résultats
|
|
|
|
- **`find_torrent()`** :
|
|
- Stocke les résultats dans `memory.episodic.store_search_results()`
|
|
- Set topic à "selecting_torrent"
|
|
- Permet la référence par index
|
|
|
|
- **`get_torrent_by_index()` (NOUVEAU)** :
|
|
- Récupère un torrent par son index dans les résultats
|
|
- Utilisé pour "télécharge le 3ème"
|
|
|
|
- **`add_torrent_by_index()` (NOUVEAU)** :
|
|
- Combine `get_torrent_by_index()` + `add_torrent_to_qbittorrent()`
|
|
- Workflow simplifié
|
|
|
|
- **`add_torrent_to_qbittorrent()`** :
|
|
- Ajoute le download dans `memory.episodic.add_active_download()`
|
|
- Set topic à "downloading"
|
|
- End workflow
|
|
|
|
#### `agent/tools/filesystem.py`
|
|
- **Suppression du paramètre `memory`** :
|
|
- `set_path_for_folder(folder_name, path_value)`
|
|
- `list_folder(folder_type, path=".")`
|
|
- Utilise `get_memory()` en interne via `FileManager`
|
|
|
|
#### `agent/tools/language.py` (NOUVEAU)
|
|
- **`set_language(language_code)`** :
|
|
- Définit la langue de conversation
|
|
- Stocke dans `memory.stm.set_language()`
|
|
- Permet au LLM de détecter et changer la langue
|
|
|
|
### 6. Exceptions (`agent/exceptions.py`)
|
|
|
|
**Nouvelles exceptions spécifiques**
|
|
|
|
- `AgentError` - Exception de base
|
|
- `ToolExecutionError(tool_name, message)` - Échec d'exécution d'un tool
|
|
- `MaxIterationsReachedError(max_iterations)` - Trop d'itérations
|
|
|
|
### 7. Config (`agent/config.py`)
|
|
|
|
**Amélioration de la validation**
|
|
|
|
- Validation stricte des valeurs (temperature, timeouts, etc.)
|
|
- Messages d'erreur plus clairs
|
|
- Docstrings complètes
|
|
- Formatage avec Black
|
|
|
|
---
|
|
|
|
## 🌐 API (`app.py`)
|
|
|
|
### Refactoring complet
|
|
|
|
**Avant** : API simple avec un seul endpoint
|
|
**Après** : API complète OpenAI-compatible avec gestion d'erreurs
|
|
|
|
### Nouveaux endpoints
|
|
|
|
1. **`GET /health`**
|
|
- Health check avec version et service name
|
|
- Retourne `{"status": "healthy", "version": "0.2.0", "service": "agent-media"}`
|
|
|
|
2. **`GET /v1/models`**
|
|
- Liste des modèles disponibles (OpenAI-compatible)
|
|
- Retourne format OpenAI avec `object: "list"`, `data: [...]`
|
|
|
|
3. **`GET /api/memory/state`**
|
|
- État complet de la mémoire (LTM + STM + Episodic)
|
|
- Pour debugging et monitoring
|
|
|
|
4. **`GET /api/memory/search-results`**
|
|
- Derniers résultats de recherche
|
|
- Permet de voir ce que l'agent a trouvé
|
|
|
|
5. **`POST /api/memory/clear`**
|
|
- Efface la session (STM + Episodic)
|
|
- Préserve la LTM (config, bibliothèque)
|
|
|
|
### Validation des messages
|
|
|
|
**Nouvelle fonction `validate_messages()`** :
|
|
- Vérifie qu'il y a au moins un message user
|
|
- Vérifie que le contenu n'est pas vide
|
|
- Raise `HTTPException(422)` si invalide
|
|
- Appelée avant chaque requête
|
|
|
|
### Gestion d'erreurs HTTP
|
|
|
|
**Codes d'erreur spécifiques** :
|
|
- **504 Gateway Timeout** : `MaxIterationsReachedError` (agent bloqué en boucle)
|
|
- **400 Bad Request** : `ToolExecutionError` (tool mal appelé)
|
|
- **502 Bad Gateway** : `LLMAPIError` (API LLM down)
|
|
- **500 Internal Server Error** : `AgentError` (erreur interne)
|
|
- **422 Unprocessable Entity** : Validation des messages
|
|
|
|
### Streaming
|
|
|
|
**Amélioration du streaming** :
|
|
- Utilise `agent.step_stream()` pour vraies réponses streamées
|
|
- Gestion correcte des chunks
|
|
- Envoi de `[DONE]` à la fin
|
|
- Gestion d'erreurs dans le stream
|
|
|
|
---
|
|
|
|
## 🧠 Infrastructure
|
|
|
|
### Persistence (`infrastructure/persistence/`)
|
|
|
|
#### `memory.py`
|
|
**Nouvelles méthodes** :
|
|
- `get_full_state()` - Retourne tout l'état de la mémoire
|
|
- `clear_session()` - Efface STM + Episodic, garde LTM
|
|
|
|
#### `context.py`
|
|
**Singleton global** :
|
|
- `init_memory(storage_dir)` - Initialise la mémoire
|
|
- `get_memory()` - Récupère l'instance globale
|
|
- `set_memory(memory)` - Définit l'instance (pour tests)
|
|
|
|
### Filesystem (`infrastructure/filesystem/`)
|
|
|
|
#### `file_manager.py`
|
|
- **Suppression du paramètre `memory`** du constructeur
|
|
- Utilise `get_memory()` en interne
|
|
- Simplifie l'utilisation
|
|
|
|
---
|
|
|
|
## 🧪 Tests
|
|
|
|
### Fixtures (`tests/conftest.py`)
|
|
|
|
**Mise à jour complète des mocks** :
|
|
|
|
1. **`MockLLMClient`** :
|
|
- `complete()` retourne `Dict[str, Any]` (pas de tuple)
|
|
- `complete_stream()` async generator
|
|
- `set_next_response()` pour configurer les réponses
|
|
|
|
2. **`MockDeepSeekClient` global** :
|
|
- Ajout de `complete_stream()` async
|
|
- Évite les appels API réels dans tous les tests
|
|
|
|
3. **Nouvelles fixtures** :
|
|
- `mock_agent_step` - Pour mocker `agent.step()`
|
|
- Fixtures existantes mises à jour
|
|
|
|
### Tests corrigés
|
|
|
|
#### `test_agent.py`
|
|
- **`MockLLMClient`** adapté pour nouvelle interface
|
|
- **`test_step_stream`** : Double réponse mockée (check + stream)
|
|
- **`test_max_iterations_reached`** : Arguments valides pour `set_language`
|
|
- Suppression de tous les asserts sur `usage`
|
|
|
|
#### `test_api.py`
|
|
- **Import corrigé** : `from agent.llm.exceptions import LLMAPIError`
|
|
- **Variable `data`** ajoutée dans `test_list_models`
|
|
- **Test streaming** : Utilisation de `side_effect` au lieu de `return_value`
|
|
- Nouveaux tests pour `/health` et `/v1/models`
|
|
|
|
#### `test_prompts.py`
|
|
- Tests adaptés au nouveau format de prompt court
|
|
- Vérification de `CONVERSATION LANGUAGE` au lieu de texte long
|
|
- Tests de `build_tools_spec()` pour format OpenAI
|
|
|
|
#### `test_prompts_edge_cases.py`
|
|
- **Réécriture complète** pour nouveau prompt
|
|
- Tests de `_format_episodic_context()`
|
|
- Tests de `_format_stm_context()`
|
|
- Suppression des tests sur sections obsolètes
|
|
|
|
#### `test_registry_edge_cases.py`
|
|
- **Nom d'outil corrigé** : `find_torrents` → `find_torrent`
|
|
- Ajout de `set_language` dans la liste des tools attendus
|
|
|
|
#### `test_agent_edge_cases.py`
|
|
- **Réécriture complète** pour tool calls natifs
|
|
- Tests de `_execute_tool_call()`
|
|
- Tests de gestion d'erreurs avec tool calls
|
|
- Tests de mémoire avec tool calls
|
|
|
|
#### `test_api_edge_cases.py`
|
|
- **Tous les chemins d'endpoints corrigés** :
|
|
- `/memory/state` → `/api/memory/state`
|
|
- `/memory/episodic/search-results` → `/api/memory/search-results`
|
|
- `/memory/clear-session` → `/api/memory/clear`
|
|
- Tests de validation des messages
|
|
- Tests des nouveaux endpoints
|
|
|
|
### Configuration pytest (`pyproject.toml`)
|
|
|
|
**Migration complète de `pytest.ini` vers `pyproject.toml`**
|
|
|
|
#### Options de coverage ajoutées :
|
|
```toml
|
|
"--cov=.", # Coverage de tout le projet
|
|
"--cov-report=term-missing", # Lignes manquantes dans terminal
|
|
"--cov-report=html", # Rapport HTML dans htmlcov/
|
|
"--cov-report=xml", # Rapport XML pour CI/CD
|
|
"--cov-fail-under=80", # Échoue si < 80%
|
|
```
|
|
|
|
#### Options de performance :
|
|
```toml
|
|
"-n=auto", # Parallélisation automatique
|
|
"--strict-markers", # Validation des markers
|
|
"--disable-warnings", # Sortie plus propre
|
|
```
|
|
|
|
#### Nouveaux markers :
|
|
- `slow` - Tests lents
|
|
- `integration` - Tests d'intégration
|
|
- `unit` - Tests unitaires
|
|
|
|
#### Configuration coverage :
|
|
```toml
|
|
[tool.coverage.run]
|
|
source = ["agent", "application", "domain", "infrastructure"]
|
|
omit = ["tests/*", "*/__pycache__/*"]
|
|
|
|
[tool.coverage.report]
|
|
exclude_lines = ["pragma: no cover", "def __repr__", ...]
|
|
```
|
|
|
|
---
|
|
|
|
## 📝 Documentation
|
|
|
|
### Nouveaux fichiers
|
|
|
|
1. **`README.md`** (412 lignes)
|
|
- Documentation complète du projet
|
|
- Quick start, installation, usage
|
|
- Exemples de conversations
|
|
- Liste des tools disponibles
|
|
- Architecture et structure
|
|
- Guide de développement
|
|
- Docker et CI/CD
|
|
- API documentation
|
|
- Troubleshooting
|
|
|
|
2. **`docs/PYTEST_CONFIG.md`**
|
|
- Explication ligne par ligne de chaque option pytest
|
|
- Guide des commandes utiles
|
|
- Bonnes pratiques
|
|
- Troubleshooting
|
|
|
|
3. **`TESTS_TO_FIX.md`**
|
|
- Liste des tests à corriger (maintenant obsolète)
|
|
- Recommandations pour l'approche complète
|
|
|
|
4. **`.pytest.ini.backup`**
|
|
- Sauvegarde de l'ancien `pytest.ini`
|
|
|
|
### Fichiers mis à jour
|
|
|
|
1. **`.env`**
|
|
- Ajout de commentaires pour chaque section
|
|
- Nouvelles variables :
|
|
- `LLM_PROVIDER` - Choix entre deepseek/ollama
|
|
- `OLLAMA_BASE_URL`, `OLLAMA_MODEL`
|
|
- `MAX_TOOL_ITERATIONS`
|
|
- `MAX_HISTORY_MESSAGES`
|
|
- Organisation par catégories
|
|
|
|
2. **`.gitignore`**
|
|
- Ajout des fichiers de coverage :
|
|
- `.coverage`, `.coverage.*`
|
|
- `htmlcov/`, `coverage.xml`
|
|
- Ajout de `.pytest_cache/`
|
|
- Ajout de `memory_data/`
|
|
- Ajout de `*.backup`
|
|
|
|
---
|
|
|
|
## 🔄 Refactoring général
|
|
|
|
### Architecture
|
|
- **Séparation des responsabilités** plus claire
|
|
- **Dépendances circulaires** éliminées
|
|
- **Injection de dépendances** via `get_memory()`
|
|
- **Typage fort** avec `Protocol` et type hints
|
|
|
|
### Code quality
|
|
- **Formatage** avec Black (line-length=88)
|
|
- **Linting** avec Ruff
|
|
- **Docstrings** complètes partout
|
|
- **Logging** ajouté dans les tools
|
|
|
|
### Performance
|
|
- **Parallélisation** des tests avec pytest-xdist
|
|
- **Streaming** asynchrone pour réponses rapides
|
|
- **Mémoire** optimisée (limitation des résultats affichés)
|
|
|
|
---
|
|
|
|
## 🐛 Bugs corrigés
|
|
|
|
1. **Dépendances circulaires** :
|
|
- `agent/tools/__init__.py` ↔ `agent/registry.py`
|
|
- Solution : Imports dans `registry.py` uniquement
|
|
|
|
2. **Import manquant** :
|
|
- `LLMAPIError` dans `test_api.py`
|
|
- Solution : `from agent.llm.exceptions import LLMAPIError`
|
|
|
|
3. **Mock streaming** :
|
|
- `test_step_stream` avec liste vide
|
|
- Solution : Double réponse mockée (check + stream)
|
|
|
|
4. **Mock async generator** :
|
|
- `return_value` au lieu de `side_effect`
|
|
- Solution : `side_effect=mock_stream_generator`
|
|
|
|
5. **Nom d'outil** :
|
|
- `find_torrents` vs `find_torrent`
|
|
- Solution : Uniformisation sur `find_torrent`
|
|
|
|
6. **Validation messages** :
|
|
- Endpoints acceptaient messages vides
|
|
- Solution : `validate_messages()` avec HTTPException
|
|
|
|
7. **Décorateur mal placé** :
|
|
- `@tool` dans `language.py` causait import circulaire
|
|
- Solution : Suppression, enregistrement dans `registry.py`
|
|
|
|
8. **Imports manquants** :
|
|
- `from typing import Dict, Any` dans plusieurs fichiers
|
|
- Solution : Ajout des imports
|
|
|
|
---
|
|
|
|
## 📊 Métriques
|
|
|
|
### Avant
|
|
- Tests : ~450 (beaucoup échouaient)
|
|
- Coverage : Non mesuré
|
|
- Endpoints : 1 (`/v1/chat/completions`)
|
|
- Tools : 5
|
|
- Dépendances circulaires : Oui
|
|
- Système de prompts : Verbeux et complexe
|
|
|
|
### Après
|
|
- Tests : ~500 (tous passent ✅)
|
|
- Coverage : Configuré avec objectif 80%
|
|
- Endpoints : 6 (5 nouveaux)
|
|
- Tools : 8 (3 nouveaux)
|
|
- Dépendances circulaires : Non ✅
|
|
- Système de prompts : Simple et efficace
|
|
|
|
### Changements de code
|
|
- **Fichiers modifiés** : ~30
|
|
- **Lignes ajoutées** : ~2000
|
|
- **Lignes supprimées** : ~1500
|
|
- **Net** : +500 lignes (documentation comprise)
|
|
|
|
---
|
|
|
|
## 🚀 Améliorations futures
|
|
|
|
### Court terme
|
|
- [ ] Atteindre 100% de coverage
|
|
- [ ] Tests d'intégration end-to-end
|
|
- [ ] Benchmarks de performance
|
|
|
|
### Moyen terme
|
|
- [ ] Support de plus de LLM providers
|
|
- [ ] Interface web (OpenWebUI)
|
|
- [ ] Métriques et monitoring
|
|
|
|
### Long terme
|
|
- [ ] Multi-utilisateurs
|
|
- [ ] Plugins système
|
|
- [ ] API GraphQL
|
|
|
|
---
|
|
|
|
## 🙏 Notes
|
|
|
|
**Problème initial** : Gemini 3 Pro a introduit des dépendances circulaires et supprimé du code critique, rendant l'application non fonctionnelle.
|
|
|
|
**Solution** : Refactoring complet du système avec :
|
|
- Migration vers tool calls natifs OpenAI
|
|
- Élimination des dépendances circulaires
|
|
- Simplification du système de prompts
|
|
- Ajout de tests et documentation
|
|
- Configuration pytest professionnelle
|
|
|
|
**Résultat** : Application stable, testée, documentée et prête pour la production ! 🎉
|
|
|
|
---
|
|
|
|
**Auteur** : Claude (avec l'aide de Francwa)
|
|
**Date** : Janvier 2024
|
|
**Version** : 0.2.0
|