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

AttributValeur
Fichier source10-owui-pipes/pipe_engine.py
Version192.17+ (déclarée dans le docstring du fichier)
Type Open WebUIPipe — se présente comme un modèle GenAI
Point d'entréePipe.pipe(body, __event_emitter__)
ExécutionIn-process dans Python d'Open WebUI, asynchrone (asyncio)
Transport APIHTTP/2 strict (httpx avec http2=True), SSE streaming
Dépendances internesecho_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 :

  1. Lecture de l'Ombre SQLite : get_message_shadow(message_id, updated_at). Le Verrou de Version compare updated_at stocké vs. updated_at fourni par le middleware. Si les timestamps concordent, l'Ombre est validée.
  2. 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.
  3. 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).

Routage Cognitif Dynamique — pipe_engine.py [MODE AUTO]
flowchart TD START([Nouvelle requête]) --> LITE[Évaluation initiale\nMODEL_LITE] LITE --> NCV{new_cognitive_level\nrequested?} NCV -->|"Oui — LITE est dépassé"| TRANS["Plan de Transfert\n(Markdown structuré)"] TRANS --> MUTATION["Mutation Modèle\n(inject into new model)"] MUTATION --> FLASH[Exécution — MODEL_FLASH] NCV -->|"Non — LITE suffit"| CTX{Taux de\nRemplissage} CTX -->|"< 30% — Zone verte"| LITE_OK["✅ Continuer en LITE\n(interactions simples)"] CTX -->|"30–50% — Zone orange"| FLASH["⚡ Escalade vers MODEL_FLASH\n(moteur agentique principal)"] CTX -->|"> 50% — Zone rouge"| PRO["🔴 Escalade PRO impérative\nquelle que soit la tâche"] FLASH --> NCV2{new_cognitive_level ?} NCV2 -->|"FLASH est dépassé"| PRO NCV2 -->|"Non"| OUTPUT PRO --> OUTPUT([Génération finale]) LITE_OK --> OUTPUT classDef zone_vert fill:#064e3b,stroke:#10b981,color:#fff classDef zone_orange fill:#78350f,stroke:#f59e0b,color:#fff classDef zone_rouge fill:#7f1d1d,stroke:#ef4444,color:#fff class LITE_OK zone_vert class FLASH zone_orange class PRO zone_rouge
ModèleRôle dans le routageDé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

  1. Un appel stream au modèle cible lève une exception (erreur réseau ou quota).
  2. Le Pipe identifie les modèles inférieurs disponibles via MODEL_HIERARCHY.
  3. _mutate_context_identity() met à jour l'AEC avec le nouveau modèle.
  4. Un toast ⚡ Cascade : PRO → FLASH est émis vers l'interface.
  5. La boucle reprend immédiatement sur le modèle de repli.
  6. 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écanismeCanalFonctionnement
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] :

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() :

En-têtes Stealth — ECHO API Antigravity
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

FormatTypeContenuAction 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 :

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 :

  1. Shadow Sealing : save_message_shadow(parts_finale, updated_at) — enregistre la structure complète des parts envoyées et reçues dans message_shadows.
  2. Cognitive Sealing : save_signature_by_id(message_id, thoughtSignature) — ancre la signature dans cognitive_signatures.
  3. Tool Journal : save_cognitive_data(cumulative_hash, tool_io) — journalise les appels et sorties d'outils dans tool_journal.
  4. 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 :

Pipeline HUD Quota — pipe_engine.py v192.7
# 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.

Pour accomplir ses missions, le Pipe s'appuie sur un Arsenal d'outils spécialisés. Découvrir l'Arsenal des Outils ➔