- 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()
15 KiB
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
- Retiré
-
Nouvelle interface LLM :
- Ajout du
ProtocolLLMClientpour typage fort complete()retourneDict[str, Any](message avec tool_calls)complete_stream()retourneAsyncGeneratorpour streaming- Suppression du tuple
(response, usage)- plus de comptage de tokens
- Ajout du
-
Gestion des tool calls :
_execute_tool_call()parse les tool calls OpenAI- Gestion des
tool_call_idpour la conversation - Boucle d'itération jusqu'à réponse finale ou max iterations
- Raise
MaxIterationsReachedErrorsi 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 passermemorypartout _prepare_messages()pour construire le contexte- Sauvegarde automatique après chaque step
- Ajout des messages user/assistant dans l'historique
- Utilisation de
2. LLM Clients
agent/llm/deepseek.py
- Nouvelle signature :
complete(messages, tools=None) -> Dict[str, Any] - Streaming :
complete_stream()avechttpx.AsyncClient - Support des tool calls : Ajout de
toolsettool_choicedans le payload - Retour simplifié : Retourne directement le message, pas de tuple
- Gestion d'erreurs : Raise
LLMAPIErrorpour 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 baseLLMConfigurationError- Configuration invalideLLMAPIError- 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
@toolpour auto-enregistrement - Liste globale
_toolspour stocker les tools make_tools()appelle explicitement chaque fonction
- Décorateur
-
Suppression des imports directs :
- Plus d'imports dans
agent/tools/__init__.py - Imports dans
registry.pyau moment de l'enregistrement - Évite les boucles d'imports
- Plus d'imports dans
-
Génération automatique des schemas :
- Inspection des signatures avec
inspect - Génération des
parametersJSON Schema - Extraction de la description depuis la docstring
- Inspection des signatures avec
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
- Stocke le résultat dans
-
find_torrent():- Stocke les résultats dans
memory.episodic.store_search_results() - Set topic à "selecting_torrent"
- Permet la référence par index
- Stocke les résultats dans
-
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é
- Combine
-
add_torrent_to_qbittorrent():- Ajoute le download dans
memory.episodic.add_active_download() - Set topic à "downloading"
- End workflow
- Ajoute le download dans
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 viaFileManager
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 baseToolExecutionError(tool_name, message)- Échec d'exécution d'un toolMaxIterationsReachedError(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
-
GET /health- Health check avec version et service name
- Retourne
{"status": "healthy", "version": "0.2.0", "service": "agent-media"}
-
GET /v1/models- Liste des modèles disponibles (OpenAI-compatible)
- Retourne format OpenAI avec
object: "list",data: [...]
-
GET /api/memory/state- État complet de la mémoire (LTM + STM + Episodic)
- Pour debugging et monitoring
-
GET /api/memory/search-results- Derniers résultats de recherche
- Permet de voir ce que l'agent a trouvé
-
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émoireclear_session()- Efface STM + Episodic, garde LTM
context.py
Singleton global :
init_memory(storage_dir)- Initialise la mémoireget_memory()- Récupère l'instance globaleset_memory(memory)- Définit l'instance (pour tests)
Filesystem (infrastructure/filesystem/)
file_manager.py
- Suppression du paramètre
memorydu constructeur - Utilise
get_memory()en interne - Simplifie l'utilisation
🧪 Tests
Fixtures (tests/conftest.py)
Mise à jour complète des mocks :
-
MockLLMClient:complete()retourneDict[str, Any](pas de tuple)complete_stream()async generatorset_next_response()pour configurer les réponses
-
MockDeepSeekClientglobal :- Ajout de
complete_stream()async - Évite les appels API réels dans tous les tests
- Ajout de
-
Nouvelles fixtures :
mock_agent_step- Pour mockeragent.step()- Fixtures existantes mises à jour
Tests corrigés
test_agent.py
MockLLMClientadapté pour nouvelle interfacetest_step_stream: Double réponse mockée (check + stream)test_max_iterations_reached: Arguments valides pourset_language- Suppression de tous les asserts sur
usage
test_api.py
- Import corrigé :
from agent.llm.exceptions import LLMAPIError - Variable
dataajoutée danstest_list_models - Test streaming : Utilisation de
side_effectau lieu dereturn_value - Nouveaux tests pour
/healthet/v1/models
test_prompts.py
- Tests adaptés au nouveau format de prompt court
- Vérification de
CONVERSATION LANGUAGEau 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_languagedans 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 :
"--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 :
"-n=auto", # Parallélisation automatique
"--strict-markers", # Validation des markers
"--disable-warnings", # Sortie plus propre
Nouveaux markers :
slow- Tests lentsintegration- Tests d'intégrationunit- Tests unitaires
Configuration coverage :
[tool.coverage.run]
source = ["agent", "application", "domain", "infrastructure"]
omit = ["tests/*", "*/__pycache__/*"]
[tool.coverage.report]
exclude_lines = ["pragma: no cover", "def __repr__", ...]
📝 Documentation
Nouveaux fichiers
-
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
-
docs/PYTEST_CONFIG.md- Explication ligne par ligne de chaque option pytest
- Guide des commandes utiles
- Bonnes pratiques
- Troubleshooting
-
TESTS_TO_FIX.md- Liste des tests à corriger (maintenant obsolète)
- Recommandations pour l'approche complète
-
.pytest.ini.backup- Sauvegarde de l'ancien
pytest.ini
- Sauvegarde de l'ancien
Fichiers mis à jour
-
.env- Ajout de commentaires pour chaque section
- Nouvelles variables :
LLM_PROVIDER- Choix entre deepseek/ollamaOLLAMA_BASE_URL,OLLAMA_MODELMAX_TOOL_ITERATIONSMAX_HISTORY_MESSAGES
- Organisation par catégories
-
.gitignore- Ajout des fichiers de coverage :
.coverage,.coverage.*htmlcov/,coverage.xml
- Ajout de
.pytest_cache/ - Ajout de
memory_data/ - Ajout de
*.backup
- Ajout des fichiers de coverage :
🔄 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
Protocolet 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
-
Dépendances circulaires :
agent/tools/__init__.py↔agent/registry.py- Solution : Imports dans
registry.pyuniquement
-
Import manquant :
LLMAPIErrordanstest_api.py- Solution :
from agent.llm.exceptions import LLMAPIError
-
Mock streaming :
test_step_streamavec liste vide- Solution : Double réponse mockée (check + stream)
-
Mock async generator :
return_valueau lieu deside_effect- Solution :
side_effect=mock_stream_generator
-
Nom d'outil :
find_torrentsvsfind_torrent- Solution : Uniformisation sur
find_torrent
-
Validation messages :
- Endpoints acceptaient messages vides
- Solution :
validate_messages()avec HTTPException
-
Décorateur mal placé :
@tooldanslanguage.pycausait import circulaire- Solution : Suppression, enregistrement dans
registry.py
-
Imports manquants :
from typing import Dict, Anydans 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