6. Les Filtres (Conscience & Mémoire)
En termes Open WebUI, un Filter est un middleware Python qui intercepte les messages
avant (hook inlet) ou après (hook outlet)
qu'ils atteignent le Pipe. ECHO en déploie trois, aux rôles complémentaires :
new_context_filter.py prépare le contexte en amont,
conversation_rag_filter.py consolide la mémoire en aval, et
edge_embed_bridge_filter.py gère l'injection de l'inférence distante WebGPU.
💡 Pourquoi trois filtres ?
La séparation des responsabilités est délibérée. Le filtre de contexte opère avant la génération : il analyse les fichiers, construit le Draft et injecte l'AEC. Le filtre mémoire opère après la génération : il distille la réponse, la vectorise et l'archive dans Qdrant. Cette architecture garantit que chaque opération coûteuse (vectorisation, distillation) n'est jamais sur le chemin critique de la génération.
Filtre 1 - new_context_filter.py
Ce filtre est la porte d'entrée cognitive d'ECHO. Il opère en deux phases : en Inlet (avant le Pipe) pour préparer le Draft, et en Outlet (après génération) pour masquer les secrets.
Phase Inlet - Pipeline d'aiguillage multimodal
Pour chaque fichier joint par l'utilisateur, le filtre détermine la stratégie de traitement
en fonction du type MIME (via get_gemini_mime) et de la taille
(seuil MAX_DIRECT_TEXT_SIZE = 256 Ko) :
MIME + Taille} AN -->|"Image/Audio/PDF/Vidéo
≤ 256 Ko"| B64["📎 Encapsulation Base64
(inlineData)"] AN -->|"Fichiers Office
(Word/Excel/PPT)"| MID["📄 Conversion MarkItDown
+ LITE Vision"] AN -->|"Texte/Code
(Zéro-RAM)"| CODEX["📝 Déplacement Vault
+ Copie Codex Git"] MID -->|"Texte Markdown"| EXTR CODEX -->|"Si ≤ 256 Ko"| TXT["📝 Injection Texte Brut"] CODEX -->|"Si > 256 Ko"| EXTR["🔍 Extraction Transmodale
(API ou Lecture Locale)"] AN -->|"Multimédia > 256 Ko"| EXTR EXTR -->|"Texte unifié"| RAG["🧱 Mémoire Vectorisée de Session & Synthèse Guidée par RAG (O(1))
(Qdrant echo_session_rag)"] AN -->|"Déjà dans le Vault
(file_id détecté)"| VAULT["📂 Référence Vault"] OWUI -->|"Images inline
(drag/paste)"| INLINE["🖼️ Extraction Ordonnée
(texte + images entrelacés)"] B64 & TXT & RAG & VAULT & INLINE --> DRAFT["📦 Draft Sémantique
(_echo_user_parts_draft)"] DRAFT -->|"Métadonnées
+ AEC YAML"| PIPE["⚙️ Pipe Engine"] classDef neutral fill:#1e293b,stroke:#475569,color:#f8fafc classDef rag fill:#064e3b,stroke:#10b981,color:#f8fafc classDef core fill:#172554,stroke:#4338ca,color:#f8fafc classDef inline fill:#4a1d96,stroke:#8b5cf6,color:#f8fafc class U,OWUI,F,AN neutral class EXTR,RAG,VAULT rag class DRAFT,PIPE core class INLINE inline
Extraction ordonnée des parts multimodales
Lorsqu'un utilisateur colle ou glisse des images directement dans le champ de saisie
d'Open WebUI (sans utiliser le file picker), OWUI construit un content
de type liste multipart au lieu d'une simple chaîne :
[{"type": "text", "text": "Voici l'image 1 :"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}},
{"type": "text", "text": "Et l'image 2 :"},
{"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}},
{"type": "text", "text": "Compare-les."}]
Le filtre itère sur cette liste en préservant l'ordre d'origine :
chaque part text et chaque part image_url est extraite
séquentiellement dans ordered_user_parts, puis injectée dans le Draft.
Cette extraction ordonnée garantit que l'entrelacement texte/image voulu par
l'utilisateur est fidèlement reproduit dans le contexte envoyé à Gemini.
⚠ Distinction fichiers inline vs. file picker
Les images inline (collées/glissées) arrivent dans
messages[-1]["content"] (liste). Les fichiers du file picker
arrivent dans body["files"] et sont traités par
_process_file_task(). Les deux chemins sont fusionnés dans le Draft
final : parts ordonnées en premier, résultats fichiers ensuite.
Mémoire Vectorisée de Session Transmodale et Synthèse Guidée par RAG (O(1))
Lorsqu'un fichier dépasse MAX_DIRECT_TEXT_SIZE (256 Ko) ou nécessite un traitement complexe, le filtre déclenche
un processus approfondi en arrière-plan : la Mémoire Vectorisée de Session.
La première phase est l'Extraction Transmodale : les fichiers textuels sont lus localement. Les fichiers Office (Word, Excel, PowerPoint) sont automatiquement convertis en Markdown via MarkItDown (avec description textuelle des images extraites par le modèle LITE). Les fichiers multimédias complexes (vidéos) sont transcrits textuellement via API.
Le texte brut unifié est immédiatement vectorisé par bge-m3 (1024d) et stocké dans
la collection echo_session_rag. Ensuite, un processus de Synthèse Guidée par RAG (O(1))
(introduit en v7.37) est exécuté. Plutôt que de distiller le document de bout en bout, le système génère une requête vectorielle stratégique ("Introduction, résumé exécutif, conclusion, thèmes clés...") pour extraire dynamiquement les SMART_CONTEXT_CHUNK_LIMIT (défaut: 10) passages les plus pertinents depuis la base Qdrant. Le résumé est ensuite généré via l'API Gemini exclusivement à partir de ces extraits. Un Fast-path (court-circuit) est appliqué si le texte est suffisamment court (ECHO_MR_SUMMARY_MAX_WORDS).
Le modèle principal reçoit uniquement le résumé exhaustif et le source_id, son statut devenant vectorized_sum_up.
Il peut interroger la donnée brute avec précision via l'outil search_sessions_context(source_id, query)
sans saturer sa fenêtre de contexte.
Ingestion Codex (Zéro-RAM)
Les fichiers code et texte pris en charge par le Codex ne sont plus conservés en mémoire vive. Ils sont instantanément déplacés (snapshot immutable) dans un espace Vault sécurisé puis copiés dans le dépôt Git local du Codex pour édition directe.
Le bloc AEC (environnement_contexte)
À la fin de la phase Inlet, le filtre génère et injecte un bloc YAML dans les métadonnées
du message. Ce bloc, délimité par <environnement_contexte>, constitue la
proprioception du modèle pour le tour courant.
environnement_contexte & evenement_systeme)<environnement_contexte>
version_framework_echo: "5.193.0"
modèle_actuel: "##MODEL_ID##"
modèle_origine: "##MODEL_ORIGIN##"
nom_utilisateur: "anonyme"
date_et_heure: "2026-05-31 14:00"
localisation: "Paris, France"
timezone: "Europe/Paris"
</environnement_contexte>
<evenement_systeme>
- type: "vectorized_sum_up"
name: "rapport.pdf"
mime: "application/pdf"
source_id: "U_abc123_C_xyz789_T_1748246400"
> Utilisez `query_registry` pour consulter l'état complet des ressources.
</evenement_systeme>
<smart_context filename="rapport.pdf" mime_type="application/pdf" mode="vectorized_sum_up" source_id="U_abc123_C_xyz789_T_1748246400">
[Résumé exhaustif et structuré généré par Synthèse Guidée par RAG]
> ⚙️ INFORMATION SYSTÈME : Les détails du fichier sont vectorisés et accessibles via `search_sessions_context`
</smart_context>
Règle d'Or - Registre Unifié
Les registres massifs (`registre_fichiers`, etc.) le modèle doit utiliser dynamiquement l'outil query_registry pour connaître l'état du système et de ses ressources (Codex, fichiers, plans). Seul un delta évènementiel (<evenement_systeme>) le prévient des actions asynchrones ou des fichiers du tour courant.
Phase Outlet - Masquage chirurgical des secrets
Après la génération, le filtre balaie le texte de la réponse via une expression régulière
correspondant au format des clés API Google (AIza[0-9A-Za-z_-]{35}).
Toute clé détectée est remplacée par la chaîne
[CLÉ API GOOGLE MASQUÉE PAR SÉCURITÉ]. Ce mécanisme garantit qu'aucun
secret ne « fuit » vers l'interface visuelle quelle que soit la réponse du modèle.
Filtre 2 - conversation_rag_filter.py
Ce filtre ne traite que la phase Outlet (post-génération). Son objectif unique est d'injecter sans latence l'historique conversationnel dans la mémoire de travail vectorisée de la session (echo_session_rag), afin d'alléger le contexte principal et de permettre l'exploration d'historiques profonds via le RAG.
Fenêtre de tour dynamique et Zéro-Latence
Le déclenchement de l'indexation s'effectue automatiquement à chaque fin de tour (lorsque l'assistant a terminé sa réponse et ses actions).
Le filtre extrait dynamiquement la fenêtre complète du tour courant et lance l'indexation
en arrière-plan via asyncio.create_task. L'ancien système de fenêtre glissante a été totalement supprimé au profit de cette architecture stateless Zéro-RAM.
Pipeline Zéro-LLM et echo_ingestion.py
L'ingestion de masse est déportée via l'architecture asynchrone echo_ingestion.py (Pipeline Zéro-RAM) qui supporte le traitement vectoriel en batch, la conversion native via MarkItDown, et un fallback SQL/Git.
Le fonctionnement de ce filtre a été radicalement simplifié et optimisé par rapport à son prédécesseur (qui gérait la distillation long terme). Le processus actuel est le suivant :
- Formatage direct et Filtre Anti-Base64 : Les messages de la fenêtre sont formatés en texte brut. Le filtre assure une extraction textuelle stricte des messages multipart, bloquant spécifiquement l'ingestion accidentelle de payloads Base64 (images) vers la base vectorielle afin de préserver l'intégrité et l'espace mémoire.
-
Injection Asynchrone : Le texte formaté est transmis directement à la méthode
index_text_in_ephemeral_rag(avec l'identifiant réservéSESSION_RAG_CONVERSATION_SOURCE_ID). -
Vectorisation : L'Embedding Worker (bge-m3) calcule les embeddings et injecte silencieusement les vecteurs et le payload dans Qdrant (
echo_session_rag).
Il n'y a aucun appel LLM pour résumer ces messages. L'exécution est quasi-instantanée et préserve la latence de la réponse utilisateur, rendant le RAG de session continuellement à jour pour l'agent de planification ou d'autres outils d'analyse (ex: search_sessions_context).
✅ Déduplication d'accès (Dédoublonnage Vault)
Avant d'envoyer un fichier au Pipe, le filtre vérifie si le fichier provient déjà du Vault
local de l'utilisateur (champ vault_path dans processed_files).
Si c'est le cas, seule la référence est transmise - pas le contenu binaire.
Cette optimisation évite des ré-uploads inutiles pour des fichiers déjà indexés.
Filtre 3 - edge_embed_bridge_filter.py
Ce filtre intercepte le corps de la page Open WebUI pour injecter dynamiquement le pont JavaScript WebGPU d'inférence distante (WASM).
Il gère également un mécanisme de blocage actif (conditionné par la valve WAIT_FOR_EDGE_EMBEDDING) avec une tolérance stricte de 3 secondes pour un signe de vie, et une limite globale définie par EDGE_EMBEDDING_TIMEOUT, garantissant que le client s'est connecté au WebSocket de l'Embedding Worker avant d'afficher l'interface graphique.
Filtre 4 - user_native_context_filter.py
Filtre d'interception Inlet très précoce (Priorité 10). Son rôle est d'héberger les paramètres spécifiques de l'utilisateur (nom, localisation) et de les injecter de manière sécurisée dans les métadonnées de la requête.
Cette mécanique repose sur l'extraction dynamique de l'identité via le WAF BunkerWeb, qui propage l'identité via l'en-tête X-Webui-Name. En déportant ces informations dans ce filtre purement interface, le cœur du système (Filtre 1) est protégé contre toute désactivation accidentelle des paramètres.
Filtre 5 - app_drawer_filter.py
Filtre silencieux Inlet de très haute priorité (1000). Il a pour mission d'injecter nativement le composant Data Island UI "ECHO App Drawer" dans le DOM. Ce HUD flottant (Heads-Up Display) s'interface de manière asynchrone avec les actions des Modèles, fournissant à l'utilisateur des raccourcis asynchrones et l'accès au Vault MCP, le tout sans jamais saturer ou perturber la fenêtre de discussion active.