6. Le Pipe (Cortex Moteur)
Le pipe_engine.py (v192.15) est l'unité centrale de traitement d'ECHO.
Open WebUI le voit comme un modèle GenAI standard, mais il agit en réalité comme un
orchestrateur de contexte : il reconstruit l'historique bit-perfect depuis
SQLite, pilote la boucle d'outils de l'Arsenal, gère l'authentification et l'inférence via
deux backends (AI Studio et API Antigravity), puis scelle chaque tour dans les Ombres Riches.
Il orchestre également la politique modèle centralisée — propagée de ses
UserValves vers tous les outils via __metadata__ et le fallback
SQLite echo_settings.
💡 Le Pipe vu par un architecte
Le Pipe est le seul composant qui connaît l'état global du système à un instant donné : quelle est la version exacte de l'historique ? Quel modèle est actif ? Quel est le quota restant ? Il centralise ces informations, prend les décisions de routage, et garantit que l'IA reçoit toujours la représentation la plus fidèle possible de la conversation passée, indépendamment de ce qu'Open WebUI peut avoir modifié ou perdu.
Fiche d'identité technique
| Attribut | Valeur |
|---|---|
| Fichier source | 10-owui-pipes/pipe_engine.py |
| Version | 192.17+ (déclarée dans le docstring du fichier) |
| Type Open WebUI | Pipe — se présente comme un modèle GenAI |
| Point d'entrée | Pipe.pipe(body, __event_emitter__) |
| Exécution | In-process dans Python d'Open WebUI, asynchrone (asyncio) |
| Transport API | HTTP/2 strict (httpx avec http2=True), SSE streaming |
| Dépendances internes | echo_utils, echo_auth, echo_protocol, echo_constants, echo_ui |
La Suture Bit-Perfect — prepare_context()
La Suture est l'algorithme central du Pipe. Elle reconstruit le tableau de contents
(historique au format Gemini) à partir des Ombres SQLite, garantissant une fidélité absolue
quelle que soit la manipulation effectuée par Open WebUI sur l'historique affiché.
Hiérarchie de restauration
Pour chaque message de l'historique, le Pipe applique cette cascade :
-
Lecture de l'Ombre SQLite :
get_message_shadow(message_id, updated_at). Le Verrou de Version compareupdated_atstocké vs.updated_atfourni par le middleware. Si les timestamps concordent, l'Ombre est validée. -
Restauration ThoughtSignature :
get_signature_by_id(message_id)récupère l'Ancre Gemini 3+ scellée au tour précédent. Elle est réinjectée dans le part correspondant (p["thoughtSignature"] = sig) pour que le modèle retrouve son état de réflexion sans relire ses pensées en texte brut. - Cold Recovery (fallback) : si l'Ombre est absente ou invalidée par le Verrou de Version (Ghost Message), le Pipe reconstruit le message depuis le texte brut fourni par le middleware. Les pièces jointes binaires du tour concerné sont perdues, mais la continuité conversationnelle est préservée.
Intégration du Draft (message courant)
Pour le message utilisateur courant (jamais encore scellé), le Pipe récupère
le Draft sémantique préparé par le Filtre (metadata._echo_user_parts_draft)
et le fusionne avec le texte brut pour construire les parts envoyées à Gemini.
C'est ici que les placeholders ##MODEL_ID##, ##MODEL_ORIGIN##
et ##ECHO_VERSION## sont résolus par _resolve_placeholders().
⚠ Content multipart OWUI (v192.7)
Lorsque l'utilisateur colle des images dans le champ de saisie, Open WebUI transmet
content sous forme de liste (et non de chaîne). Le filtre
extrait les text-parts et image-parts en ordre dans le Draft. Le Pipe détecte ce cas
via isinstance(content, str) : si content est une liste,
le texte est déjà intégré dans le Draft — pas de double injection.
Le scellement final applique la même garde de type : si content est
une liste, il est ignoré pour éviter de corrompre le shadow SQLite en passant
une structure non-string à _resolve_placeholders().
⚙️ Stérilisation des médias (Double Ancrage)
Les inlineData (binaires Base64) présents dans les Ombres sont ré-injectés
tels quels dans le contexte Gemini. Le contenu binaire n'est jamais retranscodé — garantie
d'une fidélité à l'octet près (bit-perfect). Les pièces jointes qui dépassent
la limite de taille de l'API sont automatiquement remplacées par leur slug Qdrant.
Routage Cognitif Dynamique
En mode AUTO, le Pipe adapte le modèle d'inférence à la complexité estimée
de la tâche et à l'état du contexte. Ce routage s'appuie sur deux variables :
le taux de remplissage de la fenêtre contextuelle et la
nature de la requête (évaluée au premier appel à MODEL_LITE).
| Modèle | Rôle dans le routage | Déclencheur |
|---|---|---|
MODEL_LITE (gemini-3.1-flash-lite) |
Réflexe — point d'entrée par défaut. | Toute requête en mode AUTO. Salutations, extractions triviales. |
MODEL_FLASH (gemini-3.5-flash) |
Moteur agentique principal. | LITE demande une escalade (new_cognitive_level). Le seuil de 30 % de contexte est un signal de vigilance pour activer préventivement le RAG éphémère — pas un basculement automatique. |
MODEL_PRO (gemini-3.1-pro-preview) |
Expertise — dernier recours uniquement. | FLASH est manifestement insuffisant, ou contexte ≥ 50 % (Loi de Corrélation Contextuelle). |
L'outil new_cognitive_level
Injecté dynamiquement dans les outils disponibles de chaque modèle, cet outil permet au modèle actif de déclarer qu'il ne peut pas traiter la tâche correctement à son niveau cognitif courant. Le Pipe intercepte cet appel, récupère le Plan de Transfert rédigé par le modèle sortant (document Markdown structurant le contexte et les objectifs pour son successeur), puis re-route la requête vers le modèle cible sans redémarrer la boucle complète. L'outil est bidirectionnel : il permet une escalade (LITE → FLASH → PRO) comme une redescente (PRO → FLASH → LITE) selon la charge de la tâche courante.
Mutation AEC lors d'un changement de modèle
Chaque transfert inter-modèle (escalade ou cascade technique) déclenche
_mutate_context_identity() : les champs modèle_actuel et
modèle_origine du bloc AEC sont patchés in-place par regex dans
le contexte actif, garantissant une proprioception cohérente dans tous les tours suivants.
Cascade Descendante Technique (v192.11)
Indépendamment de l'escalade cognitive volontaire, le Pipe gère les erreurs
d'indisponibilité API (429, 503) par une cascade descendante automatique :
PRO → FLASH → LITE. Cette cascade est distincte du mécanisme
new_cognitive_level et s'active uniquement en mode AUTO ou
AUTO_PRO.
⚙️ Séquence de cascade descendante
- Un appel stream au modèle cible lève une exception (erreur réseau ou quota).
- Le Pipe identifie les modèles inférieurs disponibles via
MODEL_HIERARCHY. _mutate_context_identity()met à jour l'AEC avec le nouveau modèle.- Un toast
⚡ Cascade : PRO → FLASHest émis vers l'interface. - La boucle reprend immédiatement sur le modèle de repli.
- Si plus aucun modèle n'est disponible : message d'erreur terminal et sortie.
Politique Modèle Centralisée — Pipe → Outils
La UserValve MODEL_SELECTION du Pipe définit la politique d'utilisation des
modèles pour tous les outils de l'Arsenal. OWUI ne propageant pas les
__metadata__ personnalisés du Pipe vers les outils, deux mécanismes
parallèles assurent la propagation :
| Mécanisme | Canal | Fonctionnement |
|---|---|---|
Injection __metadata__ |
Dans le même tour de Pipe | Les clés _echo_model_policy et _echo_enable_paid_credits
sont injectées dans __metadata__ avant chaque appel d'outil. |
Fallback SQLite echo_settings |
Persistance inter-sessions | EchoStateManager.save_setting() écrit model_policy et
enable_paid_credits dans identity.db. Les outils lisent
cette valeur via get_setting() si __metadata__ est absent. |
⚙️ Filtrage dynamique de l'enum modèle dans les outils (convert_owui_tools())
Lorsque le Pipe convertit les specs d'outils OWUI au format Gemini, il filtre
dynamiquement l'enum des paramètres de type modèle selon la politique active.
Un enum ["MODEL_LITE", "MODEL_FLASH", "MODEL_PRO"] présent dans un outil
est restreint à MODEL_ENUM_BY_POLICY[MODEL_SELECTION] :
AUTO→["MODEL_LITE", "MODEL_FLASH"]AUTO_PRO→["MODEL_LITE", "MODEL_FLASH", "MODEL_PRO"]- Modèle fixé →
["MODEL_FLASH"](ou LITE ou PRO selon la valve)
Gestion des Pensées (Chain-of-Thought)
Lorsque includeThoughts: true est envoyé dans le thinkingConfig
(via echo_protocol.build_ca_generation_config()), l'API Gemini retourne des
parts avec thought: true. StreamProcessor.process() les intercepte
et les isole du flux principal.
Isolation et affichage
Les pensées sont extraites par split_thought_process(text)
(echo_utils.py), encapsulées dans les balises <think>…</think>
et envoyées en amont du texte final dans le flux SSE. Open WebUI les affiche dans une
section repliable. Elles ne polluent jamais le texte de la réponse.
L'Ancre (ThoughtSignature)
ECHO ne réinjecte jamais le texte brut des pensées dans l'historique des
tours suivants. À la place, la thoughtSignature est scellée dans
cognitive_signatures (SQLite) et réinjectée au tour suivant
(p["thoughtSignature"] = sig).
La ThoughtSignature est un impératif de l'API Gemini 3+ : à la fin de chaque génération, l'API retourne systématiquement ce blob opaque dont la structure est entièrement interne à Google. Sa fonction : permettre au modèle de retrouver la continuité de sa réflexion entre les tours sans retranscrire ses raisonnements en texte brut.
⚠ ThoughtSignature ≠ KV Cache
Le KV Cache est une technique Google d'économie de tokens : les activations clé/valeur du prefill sont mises en cache implicite côté serveur Google pour éviter de les recalculer lors des tours suivants. C'est une optimisation d'inférence transparente, opaque pour l'appelant.
La ThoughtSignature est un artefact applicatif exposé explicitement dans la réponse API : l'appelant la reçoit, la persiste (SQLite) et la réinjecte manuellement au tour suivant. Les deux mécanismes sont entièrement distincts.
Réduction de la charge contextuelle
Ce protocole réduit la charge contextuelle de 30 % à 70 % selon la complexité des pensées, car les milliers de tokens de Chain-of-Thought ne sont jamais retranscrits dans le contexte. Seul le jeton opaque (quelques dizaines d'octets) les représente.
Transport HTTP/2 et Headers Stealth
EchoGeminiClient utilise httpx avec l'option http2=True
(dépendance h2 obligatoire). Pour les requêtes vers l'API Antigravity, les en-têtes
de requête reproduisent ceux d'un IDE réel grâce à get_stealth_headers() :
User-Agent: antigravity/2.1.0 (language_server; os_type=Windows; os_version=10.0.26100; arch=x64)
x-goog-api-client: antigravity/2.1.0
Authorization: Bearer {oauth_token}
Content-Type: application/json
Bridge _TOOLS_CACHE — Partage des Callables Outils
OWUI injecte le dictionnaire __tools__ (contenant les callables Python réels
des outils associés au modèle) dans le Pipe, mais ne le propage pas
aux tool callables (chaque outil reçoit un __metadata__ indépendant).
Cette contrainte architecturale empêche delegate_to_subagent d'accéder aux
callables des autres outils pour les exécuter au nom du sous-agent.
⚙️ Solution — Cache module-level
Le Pipe stocke __tools__[chat_id] dans _TOOLS_CACHE,
un dictionnaire module-level défini dans pipe_engine.py. Comme le Pipe
et tous les outils s'exécutent dans le même processus Python (uvicorn),
delegate_tool y accède via
sys.modules["function_pipe_engine"]._TOOLS_CACHE[chat_id].
Ce cache persiste tant que le processus tourne ; il est reconstitué au premier message
de chaque chat après un redémarrage.
Trois formes de __tools__ gérées
| Format | Type | Contenu | Action Pipe |
|---|---|---|---|
| Cas 1 — Production ECHO | dict {fn_name: {callable, spec, tool_id, metadata}} |
Callables Python directement utilisables | Stockage dans _TOOLS_CACHE[chat_id] + construction _echo_body_tools (specs OpenAI) |
| Cas 2 — Doc officielle OWUI | list[ToolUserModel] avec attribut .specs |
Specs OpenAI sans callables | Extraction des specs dans _echo_body_tools uniquement |
| Cas 3 — Aucun outil | None ou vide |
— | Fallback : _echo_body_tools = body.get("tools", []) |
Clés injectées dans __metadata__ par ce mécanisme :
_echo_tools_dict— dict brut OWUI (peut être vide)_echo_body_tools— specs format OpenAI pour tous les outils disponibles
Phase de Scellement
Une fois la génération terminée et le stream SSE clos, le Pipe scelle le tour en quatre opérations atomiques :
-
Shadow Sealing :
save_message_shadow(parts_finale, updated_at)— enregistre la structure complète despartsenvoyées et reçues dansmessage_shadows. -
Cognitive Sealing :
save_signature_by_id(message_id, thoughtSignature)— ancre la signature danscognitive_signatures. -
Tool Journal :
save_cognitive_data(cumulative_hash, tool_io)— journalise les appels et sorties d'outils danstool_journal. -
Suture Index :
index_suture(cumulative_hash, parent_hash)— met à jour la chaîne Merkle de la conversation.
HUD Quota — intégration dans le Pipe
À chaque fin de tour, le Pipe collecte les métriques de quota et les injecte dans la Context Gauge. Le pipeline précis, extrait du code source :
# 1. Résolution de l'ID modèle AGY (cloudcode-pa interne)
ca_id = get_ca_model_id(target_model)
# ex : MODEL_FLASH → "gemini-3-flash-agent"
# 2. Lecture du quota depuis identity.db (EchoAuth.get_model_quota)
model_quota = await echo_auth.get_model_quota(ca_id)
# Retourne le quotaInfo spécifique ou {} si modèle inconnu
# 3. Extraction des champs
quota_fraction = model_quota.get("remainingFraction", 1.0)
quota_reset = model_quota.get("resetTime", "N/A") # → formaté "HH:MM (Xmin)"
quota_rpd_rem = model_quota.get("requestsPerDayRemaining", "N/A")
quota_rpd_lim = model_quota.get("requestsPerDayLimit", "N/A")
quota_rpm_rem = model_quota.get("requestsPerMinuteRemaining", "N/A")
quota_rpm_lim = model_quota.get("requestsPerMinuteLimit", "N/A")
# 4. Rafraîchissement asynchrone non-bloquant (cooldown 10 min)
asyncio.ensure_future(refresh_quota_if_needed())
# 5. Injection HUD
await EchoUI.deploy_context_gauge(events, ..., quota_model=ca_id,
quota_rpd_rem=quota_rpd_rem, quota_rpd_lim=quota_rpd_lim, ...)
✅ Exactitude — Version 192.15
La version 192.15 finalise le refactoring du status tri-état
(success / warning / error) : suppression de
clamped:true, ajout du champ reason dans
wrap_cascade_output(). La functionResponse de
new_cognitive_level est désormais structurée avec un status dict
aligné sur ce format (model_requested / model_used).
La cascade descendante technique (192.11) gère PRO→FLASH→LITE en cas d'erreur API,
avec mutation AEC cohérente (192.13). La politique modèle Pipe → outils est
propagée via __metadata__ et persistée dans echo_settings
(SQLite fallback, 192.9). Toutes les valeurs sont vérifiées contre le code source.