diff --git a/.env.example b/.env.example index fd4caa5..1ba74e5 100644 --- a/.env.example +++ b/.env.example @@ -31,7 +31,4 @@ MAX_TOOL_ITERATIONS=10 REQUEST_TIMEOUT=30 # Memory Configuration -# Number of previous messages to include in context (default: 10) -# Higher = more context but slower/more expensive -# Lower = less context but faster MAX_HISTORY_MESSAGES=10 diff --git a/ARCHITECTURE_FINALE.md b/ARCHITECTURE_FINALE.md deleted file mode 100644 index 084112b..0000000 --- a/ARCHITECTURE_FINALE.md +++ /dev/null @@ -1,308 +0,0 @@ -# 🎯 Architecture Finale - 100% DDD - -## ✅ Migration ComplĂšte TerminĂ©e - -Toute la couche de compatibilitĂ© a Ă©tĂ© supprimĂ©e. L'architecture est maintenant **100% Domain-Driven Development**. - ---- - -## 📁 Structure Finale - -``` -agent_media/ -│ -├── domain/ # 🎯 LOGIQUE MÉTIER PURE -│ ├── shared/ -│ │ ├── exceptions.py -│ │ └── value_objects.py -│ ├── movies/ -│ │ ├── entities.py -│ │ ├── value_objects.py -│ │ ├── repositories.py -│ │ ├── services.py -│ │ └── exceptions.py -│ ├── tv_shows/ -│ │ ├── entities.py -│ │ ├── value_objects.py -│ │ ├── repositories.py -│ │ ├── services.py -│ │ └── exceptions.py -│ └── subtitles/ -│ ├── entities.py -│ ├── value_objects.py -│ ├── repositories.py -│ ├── services.py -│ └── exceptions.py -│ -├── infrastructure/ # 🔧 DÉTAILS TECHNIQUES -│ ├── api/ -│ │ ├── tmdb/ -│ │ ├── knaben/ -│ │ └── qbittorrent/ -│ ├── persistence/ -│ │ ├── memory.py -│ │ └── json/ -│ └── filesystem/ -│ ├── file_manager.py -│ ├── organizer.py -│ └── exceptions.py -│ -├── application/ # 🎬 USE CASES -│ ├── movies/ -│ │ ├── search_movie.py -│ │ └── dto.py -│ ├── torrents/ -│ │ ├── search_torrents.py -│ │ ├── add_torrent.py -│ │ └── dto.py -│ └── filesystem/ -│ ├── set_folder_path.py -│ ├── list_folder.py -│ └── dto.py -│ -├── agent/ # đŸ€– INTERFACE LLM -│ ├── llm/ -│ │ ├── __init__.py -│ │ └── deepseek.py -│ ├── tools/ -│ │ ├── __init__.py -│ │ ├── api.py -│ │ └── filesystem.py -│ ├── agent.py -│ ├── registry.py -│ ├── prompts.py -│ ├── parameters.py -│ └── config.py -│ -└── app.py # 🚀 FASTAPI -``` - ---- - -## 🔄 Imports Mis Ă  Jour - -### **app.py** -```python -# AVANT -from agent.memory import Memory - -# APRÈS -from infrastructure.persistence.memory import Memory -``` - -### **agent/agent.py** -```python -# AVANT -from .memory import Memory - -# APRÈS -from infrastructure.persistence.memory import Memory -``` - -### **agent/tools/api.py** -```python -# Utilise directement les use cases -from application.movies import SearchMovieUseCase -from infrastructure.api.tmdb import tmdb_client -``` - -### **agent/tools/filesystem.py** -```python -# Utilise directement les use cases -from application.filesystem import SetFolderPathUseCase -from infrastructure.filesystem import FileManager -from infrastructure.persistence.memory import Memory -``` - ---- - -## đŸ—‘ïž Fichiers SupprimĂ©s - -### **Ancienne Architecture** -``` -❌ agent/api/themoviedb.py -❌ agent/api/knaben.py -❌ agent/api/qbittorrent.py -❌ agent/api/__init__.py -❌ agent/models/tv_show.py -❌ agent/models/__init__.py -❌ agent/memory.py -``` - -### **Dossiers SupprimĂ©s** -``` -❌ agent/api/ -❌ agent/models/ -``` - ---- - -## ✅ Fichiers ConservĂ©s - -### **Agent Core** -``` -✅ agent/agent.py # Agent principal (imports mis Ă  jour) -✅ agent/registry.py # Registry des tools -✅ agent/prompts.py # Construction des prompts -✅ agent/parameters.py # SchĂ©ma des paramĂštres -✅ agent/config.py # Configuration -``` - -### **Agent LLM** -``` -✅ agent/llm/__init__.py -✅ agent/llm/deepseek.py # Client DeepSeek -``` - -### **Agent Tools** -``` -✅ agent/tools/__init__.py -✅ agent/tools/api.py # Wrappers vers use cases -✅ agent/tools/filesystem.py # Wrappers vers use cases -``` - -### **Application** -``` -✅ app.py # FastAPI (imports mis Ă  jour) -``` - ---- - -## 🎯 Flux de DonnĂ©es - -``` -USER - ↓ -LibreChat - ↓ -app.py (FastAPI) - ↓ -Agent (agent/agent.py) - ↓ -Tools (agent/tools/) - ↓ -Use Cases (application/) - ↓ -Domain Services (domain/) - ↓ -Infrastructure (infrastructure/) - ↓ -External APIs / Storage -``` - ---- - -## 🔑 Principes DDD AppliquĂ©s - -### **1. Layered Architecture** -✅ SĂ©paration stricte : Domain → Application → Infrastructure → Interface - -### **2. Dependency Inversion** -✅ Domain ne dĂ©pend de rien -✅ Infrastructure dĂ©pend de Domain -✅ Application orchestre Domain et Infrastructure - -### **3. Bounded Contexts** -✅ Movies, TV Shows, Subtitles sont des domaines sĂ©parĂ©s - -### **4. Ubiquitous Language** -✅ Vocabulaire mĂ©tier partagĂ© (Movie, TVShow, Episode, etc.) - -### **5. Entities & Value Objects** -✅ Entities : Movie, TVShow, Episode, Subtitle -✅ Value Objects : ImdbId, MovieTitle, SeasonNumber, etc. - -### **6. Repositories** -✅ Interfaces abstraites dans domain/ -✅ ImplĂ©mentations concrĂštes dans infrastructure/ - -### **7. Domain Services** -✅ MovieService, TVShowService, SubtitleService - -### **8. Application Services (Use Cases)** -✅ SearchMovieUseCase, SearchTorrentsUseCase, etc. - ---- - -## 🚀 Commandes de Nettoyage - -### **Script Automatique** -```bash -chmod +x FINAL_CLEANUP.sh -./FINAL_CLEANUP.sh -``` - -### **Manuel** -```bash -# Supprimer les dossiers -rm -rf agent/api/ -rm -rf agent/models/ - -# Supprimer le fichier -rm -f agent/memory.py -``` - ---- - -## 📊 Statistiques - -### **Avant le Nettoyage** -- Fichiers dans agent/ : ~15 -- Couches de compatibilitĂ© : 3 (api, models, memory) -- Architecture : Hybride - -### **AprĂšs le Nettoyage** -- Fichiers dans agent/ : ~8 -- Couches de compatibilitĂ© : 0 -- Architecture : 100% DDD - ---- - -## 🎉 RĂ©sultat - -### **Architecture Propre** ✅ -Plus aucune couche de compatibilitĂ© - -### **Imports Directs** ✅ -Tous les imports pointent vers la nouvelle architecture - -### **DDD Pur** ✅ -Respect strict des principes Domain-Driven Development - -### **Maintenable** ✅ -Code clair, organisĂ©, facile Ă  comprendre - -### **Évolutif** ✅ -Facile d'ajouter de nouvelles fonctionnalitĂ©s - ---- - -## 📚 Documentation - -- `DDD_PHASE1_COMPLETE.md` - Phase 1 (Domain + Infrastructure) -- `DDD_PHASE2_COMPLETE.md` - Phase 2 (Application + Agent) -- `DDD_MIGRATION_COMPLETE.md` - RĂ©capitulatif complet -- `ARCHITECTURE_FINALE.md` - Ce fichier (architecture finale) -- `DELETED_FILES.md` - Liste des fichiers supprimĂ©s - ---- - -## 🎯 Prochaines Étapes - -1. **Tester l'application** : `uvicorn app:app --reload` -2. **VĂ©rifier que tout fonctionne** -3. **Commencer Ă  utiliser la nouvelle architecture** -4. **Ajouter de nouveaux use cases si nĂ©cessaire** - ---- - -## 🏆 Mission Accomplie - -L'architecture est maintenant **100% Domain-Driven Development** ! - -✅ Aucune couche de compatibilitĂ© -✅ Imports directs vers la nouvelle architecture -✅ Code propre et maintenable -✅ PrĂȘt pour l'avenir - -🎉 **FĂ©licitations !** 🎉 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 4cee307..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,516 +0,0 @@ -# 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 diff --git a/README.md b/README.md index 1dde94e..3afe6b0 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ agent_media/ └── infrastructure/ # External services & persistence ``` -See [ARCHITECTURE_FINALE.md](ARCHITECTURE_FINALE.md) for details. +See [architecture_diagram.md](docs/architecture_diagram.md) for architectural details. ## Quick Start @@ -223,8 +223,6 @@ poetry run mypy . ### Adding a New Tool -See [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed instructions. - Quick example: ```python @@ -281,16 +279,6 @@ docker-compose logs -f docker-compose down ``` -## CI/CD - -Includes Gitea Actions workflow for: -- ✅ Linting & testing -- 🐳 Docker image building -- 📩 Container registry push -- 🚀 Deployment (optional) - -See [docs/CI_CD_GUIDE.md](docs/CI_CD_GUIDE.md) for setup instructions. - ## API Documentation ### Endpoints @@ -364,12 +352,12 @@ Clear session memories (STM + Episodic). - Verify volume mounts in Docker ### Tests failing -- See [docs/TEST_FAILURES_SUMMARY.md](docs/TEST_FAILURES_SUMMARY.md) - Run `poetry install` to ensure dependencies are up to date +- Check logs for specific error messages ## Contributing -Contributions are welcome! Please read [docs/CONTRIBUTING.md](docs/CONTRIBUTING.md) first. +Contributions are welcome! ### Development Workflow @@ -384,11 +372,11 @@ Contributions are welcome! Please read [docs/CONTRIBUTING.md](docs/CONTRIBUTING. ## Documentation -- [Architecture](ARCHITECTURE_FINALE.md) - System architecture -- [Contributing Guide](docs/CONTRIBUTING.md) - How to contribute -- [CI/CD Guide](docs/CI_CD_GUIDE.md) - Pipeline setup -- [Flowcharts](docs/flowchart.md) - System flowcharts -- [Test Failures](docs/TEST_FAILURES_SUMMARY.md) - Known test issues +- [Architecture Diagram](docs/architecture_diagram.md) - System architecture overview +- [Class Diagram](docs/class_diagram.md) - Class structure and relationships +- [Component Diagram](docs/component_diagram.md) - Component interactions +- [Sequence Diagram](docs/sequence_diagram.md) - Sequence flows +- [Flowchart](docs/flowchart.md) - System flowcharts ## License diff --git a/cleanup_old_files.sh b/cleanup_old_files.sh deleted file mode 100644 index 357843a..0000000 --- a/cleanup_old_files.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash -# Script de nettoyage des fichiers obsolĂštes aprĂšs migration DDD - -echo "đŸ—‘ïž Nettoyage des fichiers obsolĂštes..." - -# Supprimer les anciens clients API (dĂ©placĂ©s vers infrastructure/) -echo "Suppression des anciens clients API..." -rm -f agent/api/themoviedb.py -rm -f agent/api/knaben.py -rm -f agent/api/qbittorrent.py - -echo "✅ Anciens clients API supprimĂ©s" - -# Optionnel : Supprimer l'ancienne documentation -read -p "Voulez-vous supprimer l'ancienne documentation ? (y/n) " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]] -then - echo "Suppression de l'ancienne documentation..." - rm -f CHANGELOG_QUALITY.md - rm -f CLEANUP_FINAL.md - rm -f CLEANUP_SUMMARY.md - rm -f CODE_QUALITY.md - rm -f COMMANDS_REMOVAL.md - rm -f DEPENDENCY_INJECTION.md - rm -f DOCUMENTATION_INDEX.md - rm -f EXECUTIVE_SUMMARY.md - rm -f FILES_CHANGED.md - rm -f IMPROVEMENTS_SUMMARY.md - rm -f KNABEN_CLIENT.md - rm -f MIGRATION_GUIDE.md - rm -f MULTI_TOOL_EXECUTION.md - rm -f PARAMETERS.md - rm -f PROJECT_STRUCTURE.md - rm -f QUALITY_REVIEW_COMPLETE.md - rm -f README_QUALITY.md - rm -f REFACTORING_COMPLETE.md - rm -f REFACTORING_FINAL.md - rm -f REFACTORING_FOLDERS.md - rm -f REFACTORING_SUMMARY.md - rm -f SECURITY.md - rm -f TMDB_CLIENT_ARCHITECTURE.md - rm -f TMDB_CLIENT_SUMMARY.md - rm -f TOOLS_REFACTORING.md - rm -f TV_SHOWS.md - rm -f VERIFICATION.md - echo "✅ Ancienne documentation supprimĂ©e" -fi - -echo "" -echo "🎉 Nettoyage terminĂ© !" -echo "" -echo "📋 Fichiers conservĂ©s (nĂ©cessaires) :" -echo " - agent/api/__init__.py (re-exporte infrastructure)" -echo " - agent/models/__init__.py (re-exporte domain)" -echo " - agent/models/tv_show.py (compatibilitĂ©)" -echo " - agent/memory.py (re-exporte infrastructure)" -echo "" -echo "📚 Nouvelle documentation DDD :" -echo " - DDD_PHASE1_COMPLETE.md" -echo " - DDD_PHASE2_COMPLETE.md" -echo " - DDD_MIGRATION_COMPLETE.md" diff --git a/docs/architecture_diagram.md b/docs/architecture_diagram.md new file mode 100644 index 0000000..352ae50 --- /dev/null +++ b/docs/architecture_diagram.md @@ -0,0 +1,402 @@ +# Architecture Diagram - Agent Media + +## System Overview + +```mermaid +flowchart TB + subgraph Client["đŸ‘€ Client"] + CHAT[Chat Interface
OpenWebUI / CLI / Custom] + end + + subgraph AgentMedia["🎬 Agent Media"] + subgraph API["API Layer"] + FASTAPI[FastAPI Server
:8000] + end + + subgraph Core["Core"] + AGENT[đŸ€– Agent
Orchestrator] + MEMORY[🧠 Memory
LTM + STM + Episodic] + end + + subgraph Tools["Tools"] + T1[📁 Filesystem] + T2[🔍 Search] + T3[âŹ‡ïž Download] + end + end + + subgraph LLM["🧠 LLM Provider"] + DEEPSEEK[DeepSeek API] + OLLAMA[Ollama
Local] + end + + subgraph External["☁ External Services"] + TMDB[(TMDB
Movie Database)] + KNABEN[(Knaben
Torrent Search)] + QBIT[qBittorrent
Download Client] + end + + subgraph Storage["đŸ’Ÿ Storage"] + JSON[(memory_data/
ltm.json)] + MEDIA[(Media Folders
/movies /tvshows)] + end + + CHAT <-->|OpenAI API| FASTAPI + FASTAPI <--> AGENT + AGENT <--> MEMORY + AGENT <--> Tools + AGENT <-->|Chat Completion| LLM + + T1 <--> MEDIA + T2 --> TMDB + T2 --> KNABEN + T3 --> QBIT + + MEMORY <--> JSON + QBIT --> MEDIA + + style AgentMedia fill:#1a1a2e,color:#fff + style AGENT fill:#ff6b6b,color:#fff + style MEMORY fill:#4ecdc4,color:#fff +``` + +## Detailed Architecture + +```mermaid +flowchart TB + subgraph Clients["Clients"] + direction LR + OWU[OpenWebUI] + CLI[CLI Client] + CURL[cURL / HTTP] + end + + subgraph LoadBalancer["Entry Point"] + NGINX[Nginx / Reverse Proxy
Optional] + end + + subgraph Application["Agent Media Application"] + direction TB + + subgraph Presentation["Presentation Layer"] + EP1["/v1/chat/completions"] + EP2["/v1/models"] + EP3["/health"] + EP4["/memory/state"] + end + + subgraph AgentLayer["Agent Layer"] + direction LR + AG[Agent] + PB[PromptBuilder] + REG[Registry] + end + + subgraph ToolsLayer["Tools Layer"] + direction LR + FS_TOOL[Filesystem Tools
set_path, list_folder] + API_TOOL[API Tools
find_torrent, add_torrent] + end + + subgraph AppLayer["Application Layer"] + direction LR + UC1[SearchMovie
UseCase] + UC2[SearchTorrents
UseCase] + UC3[AddTorrent
UseCase] + UC4[SetFolderPath
UseCase] + end + + subgraph DomainLayer["Domain Layer"] + direction LR + ENT[Entities
Movie, TVShow, Subtitle] + VO[Value Objects
ImdbId, Quality, FilePath] + REPO_INT[Repository
Interfaces] + end + + subgraph InfraLayer["Infrastructure Layer"] + direction TB + + subgraph Persistence["Persistence"] + MEM[Memory Manager] + REPO_IMPL[JSON Repositories] + end + + subgraph APIClients["API Clients"] + TMDB_C[TMDB Client] + KNAB_C[Knaben Client] + QBIT_C[qBittorrent Client] + end + + subgraph FSManager["Filesystem"] + FM[FileManager] + end + end + end + + subgraph LLMProviders["LLM Providers"] + direction LR + DS[DeepSeek
api.deepseek.com] + OL[Ollama
localhost:11434] + end + + subgraph ExternalAPIs["External APIs"] + direction LR + TMDB_API[TMDB API
api.themoviedb.org] + KNAB_API[Knaben API
knaben.eu] + QBIT_API[qBittorrent WebUI
localhost:8080] + end + + subgraph DataStores["Data Stores"] + direction LR + LTM_FILE[(ltm.json
Persistent Config)] + MEDIA_DIR[(Media Directories
/downloads /movies /tvshows)] + end + + %% Client connections + Clients --> LoadBalancer + LoadBalancer --> Presentation + + %% Internal flow + Presentation --> AgentLayer + AgentLayer --> ToolsLayer + ToolsLayer --> AppLayer + AppLayer --> DomainLayer + AppLayer --> InfraLayer + InfraLayer -.->|implements| DomainLayer + + %% Agent to LLM + AgentLayer <-->|HTTP| LLMProviders + + %% Infrastructure to External + TMDB_C -->|HTTP| TMDB_API + KNAB_C -->|HTTP| KNAB_API + QBIT_C -->|HTTP| QBIT_API + + %% Persistence + MEM <--> LTM_FILE + FM <--> MEDIA_DIR + QBIT_API --> MEDIA_DIR +``` + +## Memory System Architecture + +```mermaid +flowchart TB + subgraph MemoryManager["Memory Manager"] + direction TB + + subgraph LTM["đŸ’Ÿ Long-Term Memory"] + direction LR + LTM_DESC["Persistent across restarts
Stored in ltm.json"] + + subgraph LTM_DATA["Data"] + CONFIG["config{}
folder paths, API keys"] + PREFS["preferences{}
quality, languages"] + LIBRARY["library{}
movies[], tv_shows[]"] + FOLLOWING["following[]
watchlist"] + end + end + + subgraph STM["🧠 Short-Term Memory"] + direction LR + STM_DESC["Session-based
Cleared on restart"] + + subgraph STM_DATA["Data"] + HISTORY["conversation_history[]
last 20 messages"] + WORKFLOW["current_workflow{}
type, stage, target"] + ENTITIES["extracted_entities{}
title, year, quality"] + TOPIC["current_topic
searching, downloading"] + end + end + + subgraph EPISODIC["⚡ Episodic Memory"] + direction LR + EPIS_DESC["Transient state
Cleared on restart"] + + subgraph EPIS_DATA["Data"] + SEARCH["last_search_results{}
indexed torrents"] + DOWNLOADS["active_downloads[]
in-progress"] + ERRORS["recent_errors[]
last 5 errors"] + PENDING["pending_question{}
awaiting user input"] + EVENTS["background_events[]
notifications"] + end + end + end + + subgraph Storage["Storage"] + FILE[(memory_data/ltm.json)] + end + + subgraph Lifecycle["Lifecycle"] + SAVE[save()] + LOAD[load()] + CLEAR[clear_session()] + end + + LTM <-->|read/write| FILE + SAVE --> LTM + LOAD --> LTM + CLEAR --> STM + CLEAR --> EPISODIC + + style LTM fill:#4caf50,color:#fff + style STM fill:#2196f3,color:#fff + style EPISODIC fill:#ff9800,color:#fff +``` + +## Request Flow + +```mermaid +flowchart LR + subgraph Request["1ïžâƒŁ Request"] + USER[User Message] + end + + subgraph Parse["2ïžâƒŁ Parse"] + FASTAPI[FastAPI
Extract message] + end + + subgraph Context["3ïžâƒŁ Build Context"] + PROMPT[PromptBuilder
+ Memory context
+ Tool descriptions] + end + + subgraph Think["4ïžâƒŁ Think"] + LLM[LLM
Decide action] + end + + subgraph Act["5ïžâƒŁ Act"] + TOOL[Execute Tool
or respond] + end + + subgraph Store["6ïžâƒŁ Store"] + MEM[Update Memory
STM + Episodic] + end + + subgraph Response["7ïžâƒŁ Response"] + RESP[JSON Response] + end + + USER --> FASTAPI --> PROMPT --> LLM + LLM -->|Tool call| TOOL --> MEM --> LLM + LLM -->|Text response| MEM --> RESP + + style Think fill:#ff6b6b,color:#fff + style Act fill:#4ecdc4,color:#fff + style Store fill:#45b7d1,color:#fff +``` + +## Deployment Architecture + +```mermaid +flowchart TB + subgraph Host["Host Machine"] + subgraph Docker["Docker (Optional)"] + AGENT_CONTAINER[Agent Media
Container] + end + + subgraph Native["Native Services"] + QBIT_SERVICE[qBittorrent
:8080] + OLLAMA_SERVICE[Ollama
:11434] + end + + subgraph Storage["Local Storage"] + CONFIG_DIR[/config
memory_data/] + MEDIA_DIR[/media
downloads, movies, tvshows] + end + end + + subgraph Cloud["Cloud Services"] + DEEPSEEK[DeepSeek API] + TMDB[TMDB API] + KNABEN[Knaben API] + end + + subgraph Client["Client"] + BROWSER[Browser
OpenWebUI] + end + + BROWSER <-->|:8000| AGENT_CONTAINER + AGENT_CONTAINER <-->|:8080| QBIT_SERVICE + AGENT_CONTAINER <-->|:11434| OLLAMA_SERVICE + AGENT_CONTAINER <--> CONFIG_DIR + AGENT_CONTAINER <--> MEDIA_DIR + QBIT_SERVICE --> MEDIA_DIR + + AGENT_CONTAINER <-->|HTTPS| Cloud +``` + +## Technology Stack + +```mermaid +mindmap + root((Agent Media)) + API + FastAPI + Uvicorn + OpenAI Compatible + Agent + Python 3.11+ + Dataclasses + Protocol typing + LLM + DeepSeek + Ollama + OpenAI compatible + Storage + JSON files + Filesystem + External APIs + TMDB + Knaben + qBittorrent WebUI + Architecture + DDD + Clean Architecture + Hexagonal +``` + +## Security Considerations + +```mermaid +flowchart TB + subgraph Security["Security Layers"] + direction TB + + subgraph Input["Input Validation"] + PATH_VAL[Path Traversal Protection
FileManager._sanitize_path] + INPUT_VAL[Input Sanitization
Tool parameters] + end + + subgraph Auth["Authentication"] + API_KEYS[API Keys
Environment variables] + QBIT_AUTH[qBittorrent Auth
Username/Password] + end + + subgraph Access["Access Control"] + FOLDER_RESTRICT[Folder Restrictions
Configured paths only] + SAFE_PATH[Safe Path Checks
_is_safe_path()] + end + end + + subgraph Env["Environment"] + ENV_FILE[.env file
DEEPSEEK_API_KEY
TMDB_API_KEY
QBITTORRENT_*] + end + + ENV_FILE --> Auth + Input --> Access +``` + +## Legend + +| Icon | Meaning | +|------|---------| +| 🎬 | Agent Media System | +| đŸ€– | AI Agent | +| 🧠 | Memory / LLM | +| đŸ’Ÿ | Persistent Storage | +| ⚡ | Transient / Fast | +| 📁 | Filesystem | +| 🔍 | Search | +| âŹ‡ïž | Download | +| ☁ | Cloud / External | +| đŸ‘€ | User / Client | diff --git a/docs/class_diagram.md b/docs/class_diagram.md new file mode 100644 index 0000000..cedcf91 --- /dev/null +++ b/docs/class_diagram.md @@ -0,0 +1,367 @@ +# Class Diagram - Agent Media + +```mermaid +classDiagram + direction TB + + %% =========================================== + %% MEMORY SYSTEM + %% =========================================== + + class Memory { + +Path storage_dir + +Path ltm_file + +LongTermMemory ltm + +ShortTermMemory stm + +EpisodicMemory episodic + +__init__(storage_dir: str) + +save() void + +get_context_for_prompt() Dict + +get_full_state() Dict + +clear_session() void + } + + class LongTermMemory { + +Dict config + +Dict preferences + +Dict~str, List~ library + +List~Dict~ following + +get_config(key: str) Any + +set_config(key: str, value: Any) void + +has_config(key: str) bool + +add_to_library(media_type: str, media: Dict) void + +get_library(media_type: str) List + +follow_show(show: Dict) void + +to_dict() Dict + +from_dict(data: Dict)$ LongTermMemory + } + + class ShortTermMemory { + +List~Dict~ conversation_history + +Dict current_workflow + +Dict extracted_entities + +str current_topic + +int max_history + +add_message(role: str, content: str) void + +get_recent_history(n: int) List + +start_workflow(type: str, target: Dict) void + +update_workflow_stage(stage: str) void + +end_workflow() void + +set_entity(key: str, value: Any) void + +get_entity(key: str) Any + +clear() void + +to_dict() Dict + } + + class EpisodicMemory { + +Dict last_search_results + +List~Dict~ active_downloads + +List~Dict~ recent_errors + +Dict pending_question + +List~Dict~ background_events + +store_search_results(query: str, results: List) void + +get_result_by_index(index: int) Dict + +get_search_results() Dict + +add_active_download(download: Dict) void + +complete_download(task_id: str, path: str) Dict + +add_error(action: str, error: str) void + +set_pending_question(question: str, options: List) void + +resolve_pending_question(index: int) Dict + +add_background_event(type: str, data: Dict) void + +get_unread_events() List + +clear() void + +to_dict() Dict + } + + Memory *-- LongTermMemory : ltm + Memory *-- ShortTermMemory : stm + Memory *-- EpisodicMemory : episodic + + %% =========================================== + %% AGENT SYSTEM + %% =========================================== + + class Agent { + +LLMClient llm + +Memory memory + +Dict~str, Tool~ tools + +PromptBuilder prompt_builder + +int max_tool_iterations + +__init__(llm: LLMClient, memory: Memory) + +step(user_input: str) str + -_parse_intent(text: str) Dict + -_execute_action(intent: Dict) Dict + -_check_unread_events() str + } + + class LLMClient { + <> + +complete(messages: List) str + } + + class DeepSeekClient { + +str api_key + +str model + +str base_url + +complete(messages: List) str + } + + class OllamaClient { + +str base_url + +str model + +complete(messages: List) str + } + + class PromptBuilder { + +Dict~str, Tool~ tools + +__init__(tools: Dict) + +build_system_prompt(memory: Memory) str + -_format_tools_description() str + -_format_episodic_context(memory: Memory) str + -_format_stm_context(memory: Memory) str + } + + class Tool { + <> + +str name + +str description + +Callable func + +Dict parameters + } + + Agent --> LLMClient : uses + Agent --> Memory : uses + Agent --> PromptBuilder : uses + Agent --> Tool : executes + DeepSeekClient ..|> LLMClient + OllamaClient ..|> LLMClient + PromptBuilder --> Tool : formats + + %% =========================================== + %% DOMAIN - MOVIES + %% =========================================== + + class Movie { + <> + +ImdbId imdb_id + +MovieTitle title + +ReleaseYear release_year + +Quality quality + +FilePath file_path + +FileSize file_size + +int tmdb_id + +datetime added_at + } + + class MovieTitle { + <> + +str value + +__init__(value: str) + } + + class ReleaseYear { + <> + +int value + +__init__(value: int) + } + + class Quality { + <> + +str value + +__init__(value: str) + } + + class MovieRepository { + <> + +save(movie: Movie) void + +find_by_imdb_id(imdb_id: ImdbId) Movie + +find_all() List~Movie~ + +delete(imdb_id: ImdbId) bool + +exists(imdb_id: ImdbId) bool + } + + Movie --> MovieTitle + Movie --> ReleaseYear + Movie --> Quality + Movie --> ImdbId + + %% =========================================== + %% DOMAIN - TV SHOWS + %% =========================================== + + class TVShow { + <> + +ImdbId imdb_id + +str title + +int seasons_count + +ShowStatus status + +int tmdb_id + +str first_air_date + +datetime added_at + } + + class ShowStatus { + <> + CONTINUING + ENDED + UNKNOWN + +from_string(value: str)$ ShowStatus + } + + class TVShowRepository { + <> + +save(show: TVShow) void + +find_by_imdb_id(imdb_id: ImdbId) TVShow + +find_all() List~TVShow~ + +delete(imdb_id: ImdbId) bool + } + + TVShow --> ShowStatus + TVShow --> ImdbId + + %% =========================================== + %% DOMAIN - SHARED + %% =========================================== + + class ImdbId { + <> + +str value + +__init__(value: str) + +__str__() str + } + + class FilePath { + <> + +str value + +__init__(value: str) + } + + class FileSize { + <> + +int bytes + +__init__(bytes: int) + +to_human_readable() str + } + + %% =========================================== + %% INFRASTRUCTURE - PERSISTENCE + %% =========================================== + + class JsonMovieRepository { + +Memory memory + +__init__(memory: Memory) + +save(movie: Movie) void + +find_by_imdb_id(imdb_id: ImdbId) Movie + +find_all() List~Movie~ + +delete(imdb_id: ImdbId) bool + } + + class JsonTVShowRepository { + +Memory memory + +__init__(memory: Memory) + +save(show: TVShow) void + +find_by_imdb_id(imdb_id: ImdbId) TVShow + +find_all() List~TVShow~ + +delete(imdb_id: ImdbId) bool + } + + JsonMovieRepository ..|> MovieRepository + JsonTVShowRepository ..|> TVShowRepository + JsonMovieRepository --> Memory + JsonTVShowRepository --> Memory + + %% =========================================== + %% INFRASTRUCTURE - API CLIENTS + %% =========================================== + + class TMDBClient { + +str api_key + +str base_url + +search_movie(title: str) TMDBSearchResult + +search_tv(title: str) TMDBSearchResult + +get_external_ids(tmdb_id: int) Dict + } + + class KnabenClient { + +str base_url + +search(query: str, limit: int) List~TorrentResult~ + } + + class QBittorrentClient { + +str host + +str username + +str password + +add_torrent(magnet: str) bool + +get_torrents() List + } + + %% =========================================== + %% INFRASTRUCTURE - FILESYSTEM + %% =========================================== + + class FileManager { + +Memory memory + +__init__(memory: Memory) + +set_folder_path(name: str, path: str) Dict + +list_folder(type: str, path: str) Dict + +move_file(source: str, dest: str) Dict + } + + FileManager --> Memory + + %% =========================================== + %% APPLICATION - USE CASES + %% =========================================== + + class SearchMovieUseCase { + +TMDBClient tmdb_client + +execute(title: str) SearchMovieResponse + } + + class SearchTorrentsUseCase { + +KnabenClient knaben_client + +execute(title: str, limit: int) SearchTorrentsResponse + } + + class AddTorrentUseCase { + +QBittorrentClient qbittorrent_client + +execute(magnet: str) AddTorrentResponse + } + + class SetFolderPathUseCase { + +FileManager file_manager + +execute(folder_name: str, path: str) SetFolderPathResponse + } + + class ListFolderUseCase { + +FileManager file_manager + +execute(folder_type: str, path: str) ListFolderResponse + } + + SearchMovieUseCase --> TMDBClient + SearchTorrentsUseCase --> KnabenClient + AddTorrentUseCase --> QBittorrentClient + SetFolderPathUseCase --> FileManager + ListFolderUseCase --> FileManager +``` + +## Legend + +| Symbol | Meaning | +|--------|---------| +| `<>` | Domain entity with identity | +| `<>` | Immutable value object | +| `<>` | Abstract interface/protocol | +| `<>` | Enumeration | +| `<>` | Python dataclass | +| `<>` | Python Protocol (structural typing) | +| `*--` | Composition (owns) | +| `-->` | Association (uses) | +| `..\|>` | Implementation | + +## Architecture Layers + +1. **Domain Layer** - Business entities and rules (Movie, TVShow, ValueObjects) +2. **Application Layer** - Use cases orchestrating business logic +3. **Infrastructure Layer** - External services (APIs, filesystem, persistence) +4. **Agent Layer** - AI agent, LLM clients, tools, prompts diff --git a/docs/component_diagram.md b/docs/component_diagram.md new file mode 100644 index 0000000..d6b6779 --- /dev/null +++ b/docs/component_diagram.md @@ -0,0 +1,311 @@ +# Component Diagram - Agent Media (DDD Architecture) + +```mermaid +C4Component + title Component Diagram - Agent Media + + Container_Boundary(agent_layer, "Agent Layer") { + Component(agent, "Agent", "Python", "Orchestrates LLM and tools") + Component(prompt_builder, "PromptBuilder", "Python", "Builds system prompts with context") + Component(registry, "Tool Registry", "Python", "Registers and binds tools") + + Component_Boundary(llm_clients, "LLM Clients") { + Component(deepseek, "DeepSeekClient", "Python", "DeepSeek API client") + Component(ollama, "OllamaClient", "Python", "Ollama local client") + } + + Component_Boundary(tools, "Tools") { + Component(api_tools, "API Tools", "Python", "find_torrent, add_torrent, etc.") + Component(fs_tools, "Filesystem Tools", "Python", "set_path, list_folder") + } + } +``` + +## Layered Architecture (DDD) + +```mermaid +flowchart TB + subgraph Presentation["🌐 Presentation Layer"] + API["FastAPI Server
/v1/chat/completions"] + end + + subgraph Agent["đŸ€– Agent Layer"] + AG[Agent] + PB[PromptBuilder] + TR[Tool Registry] + + subgraph LLM["LLM Clients"] + DS[DeepSeek] + OL[Ollama] + end + + subgraph Tools["Tools"] + AT[API Tools] + FT[Filesystem Tools] + end + end + + subgraph Application["⚙ Application Layer"] + subgraph UseCases["Use Cases"] + UC1[SearchMovieUseCase] + UC2[SearchTorrentsUseCase] + UC3[AddTorrentUseCase] + UC4[SetFolderPathUseCase] + UC5[ListFolderUseCase] + end + + subgraph DTOs["DTOs"] + DTO1[SearchMovieResponse] + DTO2[SearchTorrentsResponse] + DTO3[AddTorrentResponse] + end + end + + subgraph Domain["📩 Domain Layer"] + subgraph Movies["movies/"] + ME[Movie Entity] + MVO[MovieTitle, Quality, ReleaseYear] + MR[MovieRepository Interface] + end + + subgraph TVShows["tv_shows/"] + TE[TVShow Entity] + TVO[ShowStatus] + TR2[TVShowRepository Interface] + end + + subgraph Subtitles["subtitles/"] + SE[Subtitle Entity] + SVO[Language, SubtitleFormat] + SR[SubtitleRepository Interface] + end + + subgraph Shared["shared/"] + SH[ImdbId, FilePath, FileSize] + end + end + + subgraph Infrastructure["🔧 Infrastructure Layer"] + subgraph Persistence["persistence/"] + MEM[Memory
LTM + STM + Episodic] + JMR[JsonMovieRepository] + JTR[JsonTVShowRepository] + JSR[JsonSubtitleRepository] + end + + subgraph APIs["api/"] + TMDB[TMDBClient] + KNAB[KnabenClient] + QBIT[QBittorrentClient] + end + + subgraph FS["filesystem/"] + FM[FileManager] + end + end + + subgraph External["☁ External Services"] + TMDB_API[(TMDB API)] + KNAB_API[(Knaben API)] + QBIT_API[(qBittorrent)] + DISK[(Filesystem)] + end + + %% Connections + API --> AG + AG --> PB + AG --> TR + AG --> LLM + TR --> Tools + + AT --> UC1 + AT --> UC2 + AT --> UC3 + FT --> UC4 + FT --> UC5 + + UC1 --> TMDB + UC2 --> KNAB + UC3 --> QBIT + UC4 --> FM + UC5 --> FM + + JMR --> MEM + JTR --> MEM + JSR --> MEM + FM --> MEM + + JMR -.->|implements| MR + JTR -.->|implements| TR2 + JSR -.->|implements| SR + + TMDB --> TMDB_API + KNAB --> KNAB_API + QBIT --> QBIT_API + FM --> DISK + MEM --> DISK + + %% Styling + classDef presentation fill:#e1f5fe + classDef agent fill:#fff3e0 + classDef application fill:#f3e5f5 + classDef domain fill:#e8f5e9 + classDef infrastructure fill:#fce4ec + classDef external fill:#f5f5f5 + + class API presentation + class AG,PB,TR,DS,OL,AT,FT agent + class UC1,UC2,UC3,UC4,UC5,DTO1,DTO2,DTO3 application + class ME,MVO,MR,TE,TVO,TR2,SE,SVO,SR,SH domain + class MEM,JMR,JTR,JSR,TMDB,KNAB,QBIT,FM infrastructure + class TMDB_API,KNAB_API,QBIT_API,DISK external +``` + +## Memory Architecture + +```mermaid +flowchart LR + subgraph Memory["Memory System"] + direction TB + + subgraph LTM["đŸ’Ÿ Long-Term Memory
(Persistent - JSON)"] + CONFIG[config
download_folder, tvshow_folder...] + PREFS[preferences
quality, languages...] + LIB[library
movies[], tv_shows[]] + FOLLOW[following
watchlist] + end + + subgraph STM["🧠 Short-Term Memory
(Session - RAM)"] + HIST[conversation_history] + WORKFLOW[current_workflow] + ENTITIES[extracted_entities] + TOPIC[current_topic] + end + + subgraph EPISODIC["⚡ Episodic Memory
(Transient - RAM)"] + SEARCH[last_search_results
indexed torrents] + DOWNLOADS[active_downloads] + ERRORS[recent_errors] + PENDING[pending_question] + EVENTS[background_events] + end + end + + subgraph Storage["Storage"] + JSON[(ltm.json)] + end + + LTM -->|save| JSON + JSON -->|load| LTM + + STM -.->|cleared on| RESTART[Server Restart] + EPISODIC -.->|cleared on| RESTART +``` + +## Data Flow + +```mermaid +flowchart LR + subgraph Input + USER[User Request] + end + + subgraph Processing + direction TB + FASTAPI[FastAPI] + AGENT[Agent] + TOOLS[Tools] + USECASES[Use Cases] + end + + subgraph External + direction TB + TMDB[(TMDB)] + KNABEN[(Knaben)] + QBIT[(qBittorrent)] + end + + subgraph Memory + direction TB + LTM[(LTM)] + STM[(STM)] + EPIS[(Episodic)] + end + + USER -->|HTTP POST| FASTAPI + FASTAPI -->|step()| AGENT + AGENT -->|execute| TOOLS + TOOLS -->|call| USECASES + + USECASES -->|search| TMDB + USECASES -->|search| KNABEN + USECASES -->|add| QBIT + + AGENT <-->|read/write| LTM + AGENT <-->|read/write| STM + TOOLS <-->|read/write| EPIS + + AGENT -->|response| FASTAPI + FASTAPI -->|JSON| USER +``` + +## Dependency Direction + +```mermaid +flowchart BT + subgraph External["External"] + EXT[APIs, Filesystem] + end + + subgraph Infra["Infrastructure"] + INF[Clients, Repositories, Memory] + end + + subgraph App["Application"] + APP[Use Cases, DTOs] + end + + subgraph Dom["Domain"] + DOM[Entities, Value Objects, Interfaces] + end + + subgraph Agent["Agent"] + AGT[Agent, Tools, Prompts] + end + + subgraph Pres["Presentation"] + PRES[FastAPI] + end + + EXT --> Infra + Infra --> App + Infra -.->|implements| Dom + App --> Dom + Agent --> App + Agent --> Infra + Pres --> Agent + + style Dom fill:#e8f5e9,stroke:#4caf50,stroke-width:3px + style Infra fill:#fce4ec,stroke:#e91e63 + style App fill:#f3e5f5,stroke:#9c27b0 + style Agent fill:#fff3e0,stroke:#ff9800 + style Pres fill:#e1f5fe,stroke:#03a9f4 +``` + +## Legend + +| Layer | Responsibility | Examples | +|-------|---------------|----------| +| 🌐 **Presentation** | HTTP interface, request/response handling | FastAPI endpoints | +| đŸ€– **Agent** | AI orchestration, LLM interaction, tools | Agent, PromptBuilder, Tools | +| ⚙ **Application** | Use case orchestration, DTOs | SearchMovieUseCase, SearchTorrentsResponse | +| 📩 **Domain** | Business entities, rules, interfaces | Movie, TVShow, ImdbId, MovieRepository | +| 🔧 **Infrastructure** | External service implementations | TMDBClient, JsonMovieRepository, Memory | +| ☁ **External** | Third-party services | TMDB API, qBittorrent, Filesystem | + +## Key Principles + +1. **Dependency Inversion**: Domain defines interfaces, Infrastructure implements them +2. **Clean Architecture**: Dependencies point inward (toward Domain) +3. **Separation of Concerns**: Each layer has a single responsibility +4. **Memory Segregation**: LTM (persistent), STM (session), Episodic (transient) diff --git a/docs/flowchart.md b/docs/flowchart.md new file mode 100644 index 0000000..29ae126 --- /dev/null +++ b/docs/flowchart.md @@ -0,0 +1,366 @@ +# Flowcharts - Agent Media + +## 1. Main Application Flow + +```mermaid +flowchart TD + START([Application Start]) --> INIT_MEM[Initialize Memory Context
init_memory] + INIT_MEM --> INIT_LLM{LLM Provider?} + + INIT_LLM -->|OLLAMA| OLLAMA[Create OllamaClient] + INIT_LLM -->|DEEPSEEK| DEEPSEEK[Create DeepSeekClient] + + OLLAMA --> INIT_AGENT[Create Agent] + DEEPSEEK --> INIT_AGENT + + INIT_AGENT --> INIT_TOOLS[Register Tools
make_tools] + INIT_TOOLS --> START_SERVER[Start FastAPI Server
:8000] + + START_SERVER --> WAIT_REQ[/Wait for Request/] + + WAIT_REQ --> REQ_TYPE{Request Type?} + + REQ_TYPE -->|GET /health| HEALTH[Return health status] + REQ_TYPE -->|GET /v1/models| MODELS[Return model list] + REQ_TYPE -->|GET /memory/state| MEM_STATE[Return memory state] + REQ_TYPE -->|POST /memory/clear-session| CLEAR_SESSION[Clear STM + Episodic] + REQ_TYPE -->|POST /v1/chat/completions| CHAT[Process Chat Request] + + HEALTH --> WAIT_REQ + MODELS --> WAIT_REQ + MEM_STATE --> WAIT_REQ + CLEAR_SESSION --> WAIT_REQ + CHAT --> AGENT_STEP[agent.step] + AGENT_STEP --> RETURN_RESP[Return Response] + RETURN_RESP --> WAIT_REQ +``` + +## 2. Agent Step Flow (Core Logic) + +```mermaid +flowchart TD + START([agent.step called]) --> GET_MEM[Get Memory from Context] + GET_MEM --> CHECK_EVENTS[Check Unread Events] + + CHECK_EVENTS --> HAS_EVENTS{Has Events?} + HAS_EVENTS -->|Yes| FORMAT_EVENTS[Format Event Notifications] + HAS_EVENTS -->|No| BUILD_PROMPT + FORMAT_EVENTS --> BUILD_PROMPT + + BUILD_PROMPT[Build System Prompt
with Memory Context] + BUILD_PROMPT --> INIT_MSGS[Initialize Messages Array] + + INIT_MSGS --> ADD_SYSTEM[Add System Prompt] + ADD_SYSTEM --> GET_HISTORY[Get STM History] + GET_HISTORY --> ADD_HISTORY[Add History Messages] + ADD_HISTORY --> ADD_NOTIF{Has Notifications?} + + ADD_NOTIF -->|Yes| ADD_NOTIF_MSG[Add Notification Message] + ADD_NOTIF -->|No| ADD_USER + ADD_NOTIF_MSG --> ADD_USER[Add User Input] + + ADD_USER --> LOOP_START[/Tool Execution Loop/] + + LOOP_START --> CHECK_ITER{iteration < max?} + CHECK_ITER -->|No| MAX_REACHED[Request Final Response] + CHECK_ITER -->|Yes| CALL_LLM[Call LLM.complete] + + MAX_REACHED --> FINAL_LLM[Call LLM.complete] + FINAL_LLM --> SAVE_FINAL[Save to STM History] + SAVE_FINAL --> RETURN_FINAL([Return Response]) + + CALL_LLM --> PARSE_INTENT[Parse Intent from Response] + PARSE_INTENT --> IS_TOOL{Is Tool Call?} + + IS_TOOL -->|No| SAVE_HISTORY[Save to STM History] + SAVE_HISTORY --> SAVE_LTM[Save LTM] + SAVE_LTM --> RETURN_TEXT([Return Text Response]) + + IS_TOOL -->|Yes| EXEC_TOOL[Execute Tool] + EXEC_TOOL --> ADD_RESULT[Add Tool Result to Messages] + ADD_RESULT --> INC_ITER[iteration++] + INC_ITER --> LOOP_START +``` + +## 3. Tool Execution Flow + +```mermaid +flowchart TD + START([_execute_action called]) --> GET_ACTION[Extract action name & args] + GET_ACTION --> FIND_TOOL{Tool exists?} + + FIND_TOOL -->|No| UNKNOWN[Return unknown_tool error] + UNKNOWN --> END_ERR([Return Error]) + + FIND_TOOL -->|Yes| CALL_FUNC[Call tool.func with args] + + CALL_FUNC --> EXEC_OK{Execution OK?} + + EXEC_OK -->|TypeError| BAD_ARGS[Log bad arguments error] + EXEC_OK -->|Exception| EXEC_ERR[Log execution error] + EXEC_OK -->|Success| CHECK_RESULT{Result has error?} + + BAD_ARGS --> ADD_ERR_MEM[Add error to Episodic Memory] + EXEC_ERR --> ADD_ERR_MEM + ADD_ERR_MEM --> END_ERR + + CHECK_RESULT -->|Yes| ADD_ERR_MEM2[Add error to Episodic Memory] + ADD_ERR_MEM2 --> RETURN_RESULT + CHECK_RESULT -->|No| RETURN_RESULT([Return Result]) +``` + +## 4. Prompt Building Flow + +```mermaid +flowchart TD + START([build_system_prompt called]) --> GET_MEM[Get Memory from Context] + + GET_MEM --> FORMAT_TOOLS[Format Tools Description] + FORMAT_TOOLS --> FORMAT_PARAMS[Format Parameters Description] + + FORMAT_PARAMS --> CHECK_MISSING[Check Missing Required Params] + CHECK_MISSING --> HAS_MISSING{Has Missing?} + + HAS_MISSING -->|Yes| FORMAT_MISSING[Format Missing Params Info] + HAS_MISSING -->|No| FORMAT_EPISODIC + FORMAT_MISSING --> FORMAT_EPISODIC + + FORMAT_EPISODIC[Format Episodic Context] + FORMAT_EPISODIC --> HAS_SEARCH{Has Search Results?} + + HAS_SEARCH -->|Yes| ADD_SEARCH[Add Search Results Summary] + HAS_SEARCH -->|No| CHECK_PENDING + ADD_SEARCH --> CHECK_PENDING + + CHECK_PENDING{Has Pending Question?} + CHECK_PENDING -->|Yes| ADD_PENDING[Add Pending Question] + CHECK_PENDING -->|No| CHECK_DOWNLOADS + ADD_PENDING --> CHECK_DOWNLOADS + + CHECK_DOWNLOADS{Has Active Downloads?} + CHECK_DOWNLOADS -->|Yes| ADD_DOWNLOADS[Add Downloads Status] + CHECK_DOWNLOADS -->|No| CHECK_ERRORS + ADD_DOWNLOADS --> CHECK_ERRORS + + CHECK_ERRORS{Has Recent Errors?} + CHECK_ERRORS -->|Yes| ADD_ERRORS[Add Last Error] + CHECK_ERRORS -->|No| FORMAT_STM + ADD_ERRORS --> FORMAT_STM + + FORMAT_STM[Format STM Context] + FORMAT_STM --> HAS_WORKFLOW{Has Workflow?} + + HAS_WORKFLOW -->|Yes| ADD_WORKFLOW[Add Workflow Info] + HAS_WORKFLOW -->|No| CHECK_TOPIC + ADD_WORKFLOW --> CHECK_TOPIC + + CHECK_TOPIC{Has Topic?} + CHECK_TOPIC -->|Yes| ADD_TOPIC[Add Current Topic] + CHECK_TOPIC -->|No| CHECK_ENTITIES + ADD_TOPIC --> CHECK_ENTITIES + + CHECK_ENTITIES{Has Entities?} + CHECK_ENTITIES -->|Yes| ADD_ENTITIES[Add Extracted Entities] + CHECK_ENTITIES -->|No| BUILD_FINAL + ADD_ENTITIES --> BUILD_FINAL + + BUILD_FINAL[Assemble Final Prompt] + BUILD_FINAL --> RETURN([Return System Prompt]) +``` + +## 5. Memory System Flow + +```mermaid +flowchart TD + subgraph Initialization + INIT([init_memory called]) --> CREATE_MEM[Create Memory Instance] + CREATE_MEM --> LOAD_LTM{LTM file exists?} + LOAD_LTM -->|Yes| READ_FILE[Read ltm.json] + LOAD_LTM -->|No| CREATE_DEFAULT[Create Default LTM] + READ_FILE --> PARSE_JSON{Parse OK?} + PARSE_JSON -->|Yes| RESTORE_LTM[Restore LTM from Dict] + PARSE_JSON -->|No| CREATE_DEFAULT + CREATE_DEFAULT --> CREATE_STM[Create Empty STM] + RESTORE_LTM --> CREATE_STM + CREATE_STM --> CREATE_EPIS[Create Empty Episodic] + CREATE_EPIS --> SET_CTX[Set in Context Variable] + SET_CTX --> RETURN_MEM([Return Memory]) + end + + subgraph Access + GET([get_memory called]) --> CHECK_CTX{Context has Memory?} + CHECK_CTX -->|Yes| RETURN_CTX([Return Memory]) + CHECK_CTX -->|No| RAISE_ERR[Raise RuntimeError] + end + + subgraph Save + SAVE([memory.save called]) --> SERIALIZE[Serialize LTM to Dict] + SERIALIZE --> WRITE_JSON[Write to ltm.json] + WRITE_JSON --> SAVE_OK{Write OK?} + SAVE_OK -->|Yes| DONE([Done]) + SAVE_OK -->|No| LOG_ERR[Log Error & Raise] + end +``` + +## 6. Torrent Search & Download Flow + +```mermaid +flowchart TD + subgraph Search + SEARCH_START([find_torrent called]) --> CREATE_UC[Create SearchTorrentsUseCase] + CREATE_UC --> EXEC_SEARCH[Execute Search via Knaben API] + EXEC_SEARCH --> SEARCH_OK{Results Found?} + + SEARCH_OK -->|No| RETURN_ERR([Return Error]) + SEARCH_OK -->|Yes| GET_MEM[Get Memory] + GET_MEM --> STORE_RESULTS[Store in Episodic Memory
with indexes 1,2,3...] + STORE_RESULTS --> SET_TOPIC[Set Topic: selecting_torrent] + SET_TOPIC --> RETURN_RESULTS([Return Results]) + end + + subgraph "Get by Index" + GET_START([get_torrent_by_index called]) --> GET_MEM2[Get Memory] + GET_MEM2 --> HAS_RESULTS{Has Search Results?} + + HAS_RESULTS -->|No| NO_RESULTS([Return not_found Error]) + HAS_RESULTS -->|Yes| FIND_INDEX[Find Result by Index] + FIND_INDEX --> FOUND{Found?} + + FOUND -->|No| NOT_FOUND([Return not_found Error]) + FOUND -->|Yes| RETURN_TORRENT([Return Torrent Data]) + end + + subgraph "Add by Index" + ADD_START([add_torrent_by_index called]) --> CALL_GET[Call get_torrent_by_index] + CALL_GET --> GET_OK{Got Torrent?} + + GET_OK -->|No| RETURN_GET_ERR([Return Error]) + GET_OK -->|Yes| HAS_MAGNET{Has Magnet Link?} + + HAS_MAGNET -->|No| NO_MAGNET([Return no_magnet Error]) + HAS_MAGNET -->|Yes| CALL_ADD[Call add_torrent_to_qbittorrent] + CALL_ADD --> ADD_OK{Added OK?} + + ADD_OK -->|No| RETURN_ADD_ERR([Return Error]) + ADD_OK -->|Yes| ADD_NAME[Add torrent_name to Result] + ADD_NAME --> RETURN_SUCCESS([Return Success]) + end + + subgraph "Add to qBittorrent" + QB_START([add_torrent_to_qbittorrent called]) --> CREATE_UC2[Create AddTorrentUseCase] + CREATE_UC2 --> EXEC_ADD[Execute Add via qBittorrent API] + EXEC_ADD --> QB_OK{Added OK?} + + QB_OK -->|No| QB_ERR([Return Error]) + QB_OK -->|Yes| GET_MEM3[Get Memory] + GET_MEM3 --> FIND_NAME[Find Torrent Name from Search] + FIND_NAME --> ADD_DOWNLOAD[Add to Active Downloads] + ADD_DOWNLOAD --> SET_TOPIC2[Set Topic: downloading] + SET_TOPIC2 --> END_WORKFLOW[End Current Workflow] + END_WORKFLOW --> QB_SUCCESS([Return Success]) + end +``` + +## 7. Filesystem Operations Flow + +```mermaid +flowchart TD + subgraph "Set Folder Path" + SET_START([set_path_for_folder called]) --> VALIDATE_NAME[Validate Folder Name] + VALIDATE_NAME --> NAME_OK{Valid Name?} + + NAME_OK -->|No| INVALID_NAME([Return validation_failed]) + NAME_OK -->|Yes| RESOLVE_PATH[Resolve Path] + + RESOLVE_PATH --> PATH_EXISTS{Path Exists?} + PATH_EXISTS -->|No| NOT_EXISTS([Return invalid_path]) + PATH_EXISTS -->|Yes| IS_DIR{Is Directory?} + + IS_DIR -->|No| NOT_DIR([Return invalid_path]) + IS_DIR -->|Yes| IS_READABLE{Is Readable?} + + IS_READABLE -->|No| NO_READ([Return permission_denied]) + IS_READABLE -->|Yes| GET_MEM[Get Memory] + + GET_MEM --> SET_CONFIG[Set in LTM Config] + SET_CONFIG --> SAVE_MEM[Save Memory] + SAVE_MEM --> SET_SUCCESS([Return Success]) + end + + subgraph "List Folder" + LIST_START([list_folder called]) --> VALIDATE_TYPE[Validate Folder Type] + VALIDATE_TYPE --> TYPE_OK{Valid Type?} + + TYPE_OK -->|No| INVALID_TYPE([Return validation_failed]) + TYPE_OK -->|Yes| SANITIZE[Sanitize Path] + + SANITIZE --> SAFE{Path Safe?} + SAFE -->|No| TRAVERSAL([Return forbidden]) + SAFE -->|Yes| GET_MEM2[Get Memory] + + GET_MEM2 --> GET_CONFIG[Get Folder from Config] + GET_CONFIG --> CONFIGURED{Folder Configured?} + + CONFIGURED -->|No| NOT_SET([Return folder_not_set]) + CONFIGURED -->|Yes| BUILD_TARGET[Build Target Path] + + BUILD_TARGET --> CHECK_SAFE[Check Path is Safe] + CHECK_SAFE --> SAFE2{Within Base?} + + SAFE2 -->|No| FORBIDDEN([Return forbidden]) + SAFE2 -->|Yes| TARGET_EXISTS{Target Exists?} + + TARGET_EXISTS -->|No| NOT_FOUND([Return not_found]) + TARGET_EXISTS -->|Yes| TARGET_DIR{Is Directory?} + + TARGET_DIR -->|No| NOT_A_DIR([Return not_a_directory]) + TARGET_DIR -->|Yes| LIST_DIR[List Directory Contents] + + LIST_DIR --> LIST_OK{Permission OK?} + LIST_OK -->|No| PERM_DENIED([Return permission_denied]) + LIST_OK -->|Yes| LIST_SUCCESS([Return Entries]) + end +``` + +## 8. LLM Communication Flow + +```mermaid +flowchart TD + subgraph "DeepSeek Client" + DS_START([complete called]) --> DS_BUILD[Build Request Body] + DS_BUILD --> DS_HEADERS[Set Headers with API Key] + DS_HEADERS --> DS_POST[POST to DeepSeek API] + DS_POST --> DS_OK{Response OK?} + + DS_OK -->|No| DS_ERR{Error Type?} + DS_ERR -->|401| DS_AUTH([Raise LLMAuthenticationError]) + DS_ERR -->|429| DS_RATE([Raise LLMRateLimitError]) + DS_ERR -->|Other| DS_API([Raise LLMAPIError]) + + DS_OK -->|Yes| DS_PARSE[Parse JSON Response] + DS_PARSE --> DS_EXTRACT[Extract Content] + DS_EXTRACT --> DS_RETURN([Return Content String]) + end + + subgraph "Ollama Client" + OL_START([complete called]) --> OL_BUILD[Build Request Body] + OL_BUILD --> OL_POST[POST to Ollama API] + OL_POST --> OL_OK{Response OK?} + + OL_OK -->|No| OL_ERR([Raise LLMAPIError]) + OL_OK -->|Yes| OL_PARSE[Parse JSON Response] + OL_PARSE --> OL_EXTRACT[Extract Message Content] + OL_EXTRACT --> OL_RETURN([Return Content String]) + end +``` + +## Legend + +| Symbol | Meaning | +|--------|---------| +| `([text])` | Start/End (Terminal) | +| `[text]` | Process | +| `{text}` | Decision | +| `/text/` | Input/Output | +| `-->` | Flow direction | +| `-->\|label\|` | Conditional flow | diff --git a/docs/sequence_diagram.md b/docs/sequence_diagram.md new file mode 100644 index 0000000..4864c03 --- /dev/null +++ b/docs/sequence_diagram.md @@ -0,0 +1,264 @@ +# Sequence Diagrams - Agent Media + +## 1. Torrent Search and Download Flow + +```mermaid +sequenceDiagram + autonumber + participant User + participant FastAPI as FastAPI Server + participant Agent + participant PromptBuilder + participant LLM as LLM (DeepSeek/Ollama) + participant Tools as Tool Registry + participant Memory + participant Knaben as Knaben API + participant qBit as qBittorrent + + User->>FastAPI: POST /v1/chat/completions
"Find torrents for Inception 1080p" + FastAPI->>Agent: step(user_input) + + Agent->>Memory: stm.get_recent_history() + Memory-->>Agent: conversation history + + Agent->>PromptBuilder: build_system_prompt(memory) + PromptBuilder->>Memory: ltm.config, episodic state + Memory-->>PromptBuilder: context data + PromptBuilder-->>Agent: system prompt with context + + Agent->>LLM: complete(messages) + LLM-->>Agent: {"action": {"name": "find_torrents", "args": {...}}} + + Agent->>Agent: _parse_intent(response) + Agent->>Tools: execute find_torrents + Tools->>Knaben: search("Inception 1080p") + Knaben-->>Tools: torrent results + + Tools->>Memory: episodic.store_search_results() + Memory-->>Tools: stored with indexes (1, 2, 3...) + + Tools-->>Agent: {"status": "ok", "torrents": [...]} + + Agent->>LLM: complete(messages + tool_result) + LLM-->>Agent: "I found 5 torrents for Inception..." + + Agent->>Memory: stm.add_message("user", input) + Agent->>Memory: stm.add_message("assistant", response) + Agent->>Memory: save() + + Agent-->>FastAPI: final response + FastAPI-->>User: JSON response + + Note over User,qBit: User selects a torrent + + User->>FastAPI: POST /v1/chat/completions
"Download the 2nd one" + FastAPI->>Agent: step(user_input) + + Agent->>PromptBuilder: build_system_prompt(memory) + PromptBuilder->>Memory: episodic.last_search_results + Note right of Memory: Results still in memory:
1. Inception.2010.1080p...
2. Inception.1080p.BluRay... + Memory-->>PromptBuilder: context with search results + PromptBuilder-->>Agent: prompt showing available results + + Agent->>LLM: complete(messages) + LLM-->>Agent: {"action": {"name": "add_torrent_by_index", "args": {"index": 2}}} + + Agent->>Tools: execute add_torrent_by_index(index=2) + Tools->>Memory: episodic.get_result_by_index(2) + Memory-->>Tools: torrent data with magnet link + + Tools->>qBit: add_torrent(magnet_link) + qBit-->>Tools: success + + Tools->>Memory: episodic.add_active_download() + Tools-->>Agent: {"status": "ok", "torrent_name": "Inception.1080p.BluRay"} + + Agent->>LLM: complete(messages + tool_result) + LLM-->>Agent: "I've added Inception to qBittorrent!" + + Agent-->>FastAPI: final response + FastAPI-->>User: JSON response +``` + +## 2. Folder Configuration Flow + +```mermaid +sequenceDiagram + autonumber + participant User + participant FastAPI as FastAPI Server + participant Agent + participant LLM as LLM + participant Tools as Tool Registry + participant FileManager + participant Memory + participant FS as Filesystem + + User->>FastAPI: POST /v1/chat/completions
"Set download folder to /mnt/media/downloads" + FastAPI->>Agent: step(user_input) + + Agent->>LLM: complete(messages) + LLM-->>Agent: {"action": {"name": "set_path_for_folder", "args": {...}}} + + Agent->>Tools: execute set_path_for_folder + Tools->>FileManager: set_folder_path("download", "/mnt/media/downloads") + + FileManager->>FS: Path.exists()? + FS-->>FileManager: true + FileManager->>FS: Path.is_dir()? + FS-->>FileManager: true + FileManager->>FS: os.access(R_OK)? + FS-->>FileManager: true + + FileManager->>Memory: ltm.set_config("download_folder", path) + FileManager->>Memory: save() + Memory->>FS: write ltm.json + + FileManager-->>Tools: {"status": "ok", "path": "/mnt/media/downloads"} + Tools-->>Agent: result + + Agent->>LLM: complete(messages + tool_result) + LLM-->>Agent: "Download folder set to /mnt/media/downloads" + + Agent-->>FastAPI: final response + FastAPI-->>User: JSON response +``` + +## 3. Multi-Tool Workflow (Search Movie → Find Torrents → Download) + +```mermaid +sequenceDiagram + autonumber + participant User + participant Agent + participant LLM as LLM + participant TMDB as TMDB API + participant Knaben as Knaben API + participant qBit as qBittorrent + participant Memory + + User->>Agent: "Download Dune 2 in 4K" + + rect rgb(240, 248, 255) + Note over Agent,TMDB: Step 1: Identify the movie + Agent->>LLM: complete(messages) + LLM-->>Agent: {"action": "find_media_imdb_id", "args": {"media_title": "Dune 2"}} + Agent->>TMDB: search_movie("Dune 2") + TMDB-->>Agent: {title: "Dune: Part Two", imdb_id: "tt15239678", year: 2024} + Agent->>Memory: stm.set_entity("last_media_search", {...}) + end + + rect rgb(255, 248, 240) + Note over Agent,Knaben: Step 2: Search for torrents + Agent->>LLM: complete(messages + movie_info) + LLM-->>Agent: {"action": "find_torrents", "args": {"media_title": "Dune Part Two 2024 4K"}} + Agent->>Knaben: search("Dune Part Two 2024 4K") + Knaben-->>Agent: [torrent1, torrent2, torrent3...] + Agent->>Memory: episodic.store_search_results() + end + + rect rgb(240, 255, 240) + Note over Agent,qBit: Step 3: Add best torrent + Agent->>LLM: complete(messages + torrents) + LLM-->>Agent: {"action": "add_torrent_by_index", "args": {"index": 1}} + Agent->>Memory: episodic.get_result_by_index(1) + Memory-->>Agent: torrent with magnet + Agent->>qBit: add_torrent(magnet) + qBit-->>Agent: success + Agent->>Memory: episodic.add_active_download() + end + + Agent->>LLM: complete(messages + all_results) + LLM-->>Agent: "I found Dune: Part Two (2024) and added the 4K torrent to qBittorrent!" + Agent-->>User: Final response +``` + +## 4. Error Handling Flow + +```mermaid +sequenceDiagram + autonumber + participant User + participant Agent + participant LLM as LLM + participant Tools as Tool Registry + participant Memory + participant API as External API + + User->>Agent: "Download the 5th torrent" + + Agent->>LLM: complete(messages) + LLM-->>Agent: {"action": "add_torrent_by_index", "args": {"index": 5}} + + Agent->>Tools: execute add_torrent_by_index(5) + Tools->>Memory: episodic.get_result_by_index(5) + + alt No search results + Memory-->>Tools: None (no previous search) + Tools-->>Agent: {"status": "error", "error": "not_found"} + Agent->>Memory: episodic.add_error("add_torrent_by_index", "not_found") + else Index out of range + Memory-->>Tools: None (only 3 results) + Tools-->>Agent: {"status": "error", "error": "not_found"} + Agent->>Memory: episodic.add_error("add_torrent_by_index", "not_found") + end + + Agent->>LLM: complete(messages + error) + LLM-->>Agent: "I couldn't find torrent #5. Please search for torrents first." + + Agent-->>User: Error explanation + + Note over User,API: User searches first + + User->>Agent: "Search for Matrix 1999" + Agent->>API: search("Matrix 1999") + API-->>Agent: [3 results] + Agent->>Memory: episodic.store_search_results() + Agent-->>User: "Found 3 torrents..." + + User->>Agent: "Download the 2nd one" + Agent->>Memory: episodic.get_result_by_index(2) + Memory-->>Agent: torrent data ✓ + Agent-->>User: "Added to qBittorrent!" +``` + +## 5. Background Events Flow + +```mermaid +sequenceDiagram + autonumber + participant User + participant Agent + participant Memory + participant qBit as qBittorrent + participant LLM as LLM + + Note over qBit,Memory: Background: Download completes + qBit--)Memory: episodic.complete_download(task_id, file_path) + Memory->>Memory: add_background_event("download_complete", {...}) + + Note over User,LLM: Later: User sends a message + User->>Agent: "What's new?" + + Agent->>Memory: episodic.get_unread_events() + Memory-->>Agent: [{type: "download_complete", data: {name: "Inception.1080p"}}] + + Agent->>Agent: _check_unread_events() + Note right of Agent: Formats notification:
"Download completed: Inception.1080p" + + Agent->>LLM: complete(messages + notification) + LLM-->>Agent: "Good news! Inception.1080p has finished downloading." + + Agent-->>User: Response with notification +``` + +## Legend + +| Element | Description | +|---------|-------------| +| `rect rgb(...)` | Grouped steps in a workflow | +| `alt/else` | Conditional branches | +| `Note` | Explanatory notes | +| `-->>` | Response/return | +| `->>` | Request/call | +| `--))` | Async event | diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index ca47314..0000000 --- a/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Test suite for Agent Media."""