Aller au contenu principal

Pont Cordis

Le pont Cordis intègre le moteur DeepSeek Harness (DSH) au backend de Libre WebUI. DSH s’exécute sous forme d’arbre de plugins dans un environnement Cordis hébergé par Libre WebUI : ses fonctionnalités sont donc fournies comme services Cordis plutôt que comme modules importés.

Le pont est désactivé par défaut. Rien de ce qui est décrit ici ne se produit avant son activation par un opérateur (voir Configuration Cordis).

Pourquoi un pont plutôt qu’une intégration directe

Importer les packages DSH depuis les services de Libre WebUI serait plus court, mais moins adapté. Une importation directe fait du moteur une dépendance de compilation : changer d’adaptateur de modèle, remplacer la boucle de l’agent ou supprimer le moteur impose alors de modifier et de redéployer Libre WebUI.

Le pont inverse cette relation. Libre WebUI dépend d’un seul contrat abstrait, et un document de composition Cordis détermine ce qui l’implémente :

  • Changer de cible sans reconstruire. La composition est un fichier YAML : utiliser un autre fournisseur relève donc de la configuration.
  • Configurer les fonctionnalités. Chaque fonctionnalité correspond à une entrée du chargeur. Les modifications apportées par l’opérateur à la composition prennent effet au prochain démarrage de l’hôte.
  • Tout retirer proprement. Chaque service, écouteur et effet installé par le moteur appartient à la fibre racine. La destruction de cette fibre les retire tous ; l’opérateur peut donc arrêter le moteur sans redémarrer Libre WebUI.

Couches

Les dépendances DSH concrètes restent dans backend/src/cordis/dsh/. Les routes et services applicatifs utilisent les contrats du pont. Le pilote Work possède une composition distincte en mémoire et ne monte jamais les plugins du système de fichiers de l’hôte.

Contrats

Le contrat réside dans backend/src/cordis/contracts.ts. Il est volontairement limité aux structures nécessaires à l’API Libre WebUI, exprimées sans le vocabulaire interne du moteur.

ContratRôle
DshEngine.status()État du cycle de vie de chaque service du moteur (pending / ready / failed)
DshEngine.modelConfiguration()Modèle et fournisseur par défaut de la composition active
DshEngine.listSessions()Résumés des sessions, de la plus récente à la plus ancienne
DshEngine.getSession(id)Une session avec ses messages projetés
DshEngine.createSession(opts)Réserver un identifiant de session et un répertoire de travail
DshEngine.updateSessionSettings(id, settings)Enregistrer la sélection réelle du modèle et le mode natif d’accès au système de fichiers lorsque la session est inactive
DshEngine.decideApproval(id, approvalId, decision)Résoudre une demande native d’autorisation d’outil en attente pour sa session propriétaire
DshEngine.deleteSession(id)Terminer une session et détruire son agent
DshEngine.listAgents()Agents actifs, identifiés comme racines ou enfants
DshEngine.listTools()Outils accessibles au modèle enregistrés par le moteur
DshEngine.sendMessage(id, txt)Démarrer un tour et renvoyer un handle de flux
DshEngine.cancel(id)Annuler le tour en cours d’une session

Le contrat est publié comme service Cordis libreDshEngine. Un consommateur y accède donc avec ctx.get('libreDshEngine') sans importer le module du pont.

EngineStreamChunk transporte text, reasoning, tool-call, tool-result, approval-request, approval-decision, error et done. Les trames en direct sont acheminées selon leur agent et leur session propriétaires ; le message assistant durable correspondant n’est pas émis une seconde fois. sendMessage renvoie un handle dont subscribe rejoue tout ce qui a déjà été émis : un premier jeton rapide ne peut ainsi pas se perdre entre le démarrage du tour et l’enregistrement de l’écouteur HTTP.

Séquence d’un tour de chat

NDJSON est utilisé plutôt qu’un WebSocket, car un tour est une seule séquence du serveur vers le client après la requête. Le conserver dans le POST évite une seconde négociation, un ticket et un protocole de reconnexion, et maintient tout le tour dans une seule requête authentifiée.

DONE et PENDING

Cordis active un plugin lorsque les services qu’il déclare sont disponibles. Une entrée peut donc rester dans un état où elle ne s’exécute pas encore. Deux notions distinctes comptent ; les confondre est la cause la plus fréquente d’un moteur silencieux.

État d’une entrée du chargeur. Le chargeur suit chaque entrée au fil de PENDING → LOADING → ACTIVE, ou FAILED. Si ses services déclarés manquent, l’entrée reste en attente indéfiniment au lieu d’échouer. Une composition incomplète peut ainsi démarrer un moteur qui ne fournit rien.

Disponibilité d’un service. L’hôte indique l’état de chaque service attendu :

ÉtatSignificationCause
pendingNon enregistré dans le contexteL’entrée qui le fournit n’a pas été activée ou est désactivée
readyEnregistré et utilisableL’entrée qui le fournit a été activée
failedDéclaré mais inutilisableAccompagné d’une chaîne detail

host.status() liste tous les services attendus avec leur disponibilité et nomme les services requis qui manquent ; GET /api/cordis/health expose les mêmes informations. Si une composition omet un service requis, le démarrage lève une erreur au lieu de publier un moteur qui répond par des listes vides.

Deux chaînes de dépendances prêtent facilement à confusion :

  • dsh-tools ne peut pas démarrer sans systemPrompt.
  • dsh-agent-loop ne peut pas démarrer avant que agents, sessions, llm, tools, systemPrompt et sessionProjections existent tous.

Une composition à laquelle l’un de ces services manque produit un stockage de sessions fonctionnel et un moteur qui ne répond jamais aux messages.

Configuration des fournisseurs

L’entrée fournie libre-webui-llm-adapter dessert les fournisseurs de modèles configurés dans Libre WebUI. Le sélecteur de modèles de la page du moteur choisit un modèle de fournisseur pour une session sans remplacer cette entrée.

Les modifications des entrées de plugins de la composition s’appliquent au prochain démarrage de l’hôte. Redémarrez le backend ou désactivez puis réactivez Cordis lorsque le commutateur administrateur est déverrouillé. Les sessions persistantes restent dans le stockage configuré et reprennent avec la composition actuelle.

Le code d’intégration de confiance peut utiliser directement les API de cycle de vie du chargeur Cordis. Le pont n’expose aucun point d’accès pour remplacer un adaptateur et ne rétablit pas automatiquement l’adaptateur précédent si son remplaçant échoue.

Retour arrière

Détruire la fibre racine de l’hôte retire tout ce que le moteur a installé. Cette relation de propriété constitue toute la garantie, car :

  • Les services sont enregistrés par les plugins et disparaissent donc avec leur fibre.
  • Les abonnements à session/event sont enregistrés dans le constructeur du pont et appartiennent à la fibre de son entrée.
  • Le pont suit les handles d’agents et les détruit dans son effet de nettoyage.
  • L’hôte détruit le contexte racine, qui possède toutes les entrées.

stopCordisHost() est idempotent et intégré à la séquence d’arrêt du backend : les temporisateurs et handles de fichiers du moteur sont libérés sans attendre la fin du processus.

Identité et persistance des sessions

La page du moteur réserve un identifiant opaque lors de la création d’une session. Si la persistance est activée, son en-tête est enregistré immédiatement : même une session vide survit à un redémarrage. Le pont liste les sessions stockées et actives, lit les journaux enregistrés via l’API de persistance validée de DSH et reprend l’agent avec le même identifiant lors d’un suivi. Les nouveaux messages utilisateur utilisent le constructeur de messages identifiés de DSH.

Supprimer une session annule et détruit son agent avant de retirer son artefact. L’adaptateur local de suppression JSONL valide les chemins du stockage et de la session et refuse les liens symboliques. Les backends de persistance personnalisés qui ne prennent pas en charge la suppression renvoient une erreur au lieu d’affirmer que les données ont été supprimées.

L’annulation atteint l’agent natif, la requête au modèle et les opérations des outils. La déconnexion d’un client annule son tour ; les messages terminés restent consultables. La relecture du flux mis en tampon est bornée et préserve une réponse rapide émise avant l’attachement du lecteur.

Le moteur de l’hôte est une fonctionnalité solo à réplique unique. Les déploiements d’équipe ne peuvent pas monter son environnement JSONL local. Work en bac à sable utilise à la place ses dépôts SQL existants pour les tâches, exécutions, messages, autorisations et événements.

Surface HTTP

MéthodeCheminRôle
GET/api/cordis/healthÉtat du pont ; sans authentification
GET/api/cordis/sessionsLister les sessions
POST/api/cordis/sessionsCréer une session
GET/api/cordis/sessions/:idLire une session et ses messages
DELETE/api/cordis/sessions/:idTerminer une session
POST/api/cordis/sessions/:id/messagesEnvoyer un message et recevoir un flux NDJSON
POST/api/cordis/sessions/:id/cancelAnnuler le tour en cours
GET/api/cordis/agentsLister les agents actifs
GET/api/cordis/toolsLister les outils enregistrés

Toutes les routes sauf /health exigent une session administrateur authentifiée et répondent 503 avec le code CORDIS_DISABLED, CORDIS_STARTING ou CORDIS_UNAVAILABLE tant que le pont ne peut pas traiter les requêtes.

Page Moteur Cordis de Libre WebUI présentant la liste des sessions, les outils enregistrés du moteur et une transcription de chat en continu.

La page est frontend/src/pages/CordisPage.tsx, accessible à /cordis depuis la barre latérale. Elle liste les sessions et outils enregistrés, crée des sessions et affiche les tours en continu dans la transcription. Si le pont est désactivé ou ne peut pas démarrer, elle affiche la raison plutôt qu’une liste vide, car sinon « aucune session » et « aucun moteur » seraient indiscernables.

Le client navigateur est frontend/src/utils/api/cordisApi.ts. Il communique uniquement avec cette surface et n’importe ni type backend ni package @deepseek-ai/* : le moteur peut donc être remplacé sans modifier le frontend. Un tour est consommé avec sendMessage(sessionId, text, { onChunk }) ; le client analyse lui-même le JSON délimité par des retours à la ligne et accepte les fragments répartis sur plusieurs lectures réseau.

Commandes de chat du moteur

La page du moteur affiche Markdown, tableaux et blocs de code avec coloration syntaxique, ainsi que des commandes de copie pour les réponses et le code. Les invites système et le contexte injecté par l’environnement sont regroupés dans une section Contexte de session repliée ; ils ne sont pas présentés comme des messages rédigés par l’utilisateur. Le raisonnement exposé et l’activité des outils ont leurs propres sections dépliables, et les résultats des outils restent associés à la bonne opération après rechargement.

Choisissez un véritable modèle de fournisseur dans la zone de saisie. Le sélecteur utilise les modèles locaux et de plugins disponibles pour l’administrateur connecté, avec l’identité du fournisseur. Les sélections de personas et d’agents du Chat ne sont pas des identifiants de modèles et n’injectent pas leurs instructions dans une conversation du moteur. Les anciens en-têtes de modèles de personas en échec sont ignorés comme indications de modèle par défaut sans modifier le journal enregistré.

Chaque session possède son propre réglage Lecture seule ou Écriture dans l’espace de travail, imposé par la politique de système de fichiers de DSH et les limites canoniques de l’espace de travail du pont. La zone de saisie indique le périmètre de cet espace. Les réglages sont enregistrés comme événements natifs de session et survivent aux redémarrages ; les modifications sont refusées pendant un tour actif.

Une demande native d’élévation apparaît comme une carte Autoriser une fois / Refuser attachée à l’opération. L’autorisation ne vaut que pour cette demande et laisse le mode d’autorisation permanent inchangé. Les demandes expirées ou annulées ne peuvent pas être approuvées, et les appels Chat sans interface refusent les questions qu’ils ne peuvent pas présenter. Le pont ne propose pas d’accès illimité à l’hôte.

Les points d’accès administrateur supplémentaires sont :

MéthodeCheminRôle
GET/api/cordis/modelsModèles de fournisseurs disponibles et modèle réel par défaut actuel
PATCH/api/cordis/sessions/:id/settingsDéfinir le modèle et/ou le mode d’autorisation de cette session
POST/api/cordis/sessions/:id/approvals/:approvalIdDécider d’une demande en attente avec allowed-once ou rejected

Utiliser le moteur dans Chat

Activez Accès et politiques → Modèles d’agents CLI et Moteur Cordis. Les administrateurs peuvent alors sélectionner DeepSeek Harness dans Chat. Chaque requête reçoit une nouvelle session transitoire du moteur contenant la transcription fournie pour cette requête Chat. La base de données normale du Chat reste la référence ; les conversations sans rapport, les bifurcations et les nouvelles tentatives ne peuvent pas partager un historique invisible du moteur. Le journal transitoire est supprimé après achèvement ou annulation et n’apparaît pas sur la page du moteur.

La composition standard des fournisseurs liste aussi les choix DeepSeek Harness · modèle (fournisseur) dans le groupe Agents. Leurs identifiants enregistrés encapsulent la même route de fournisseur qualifiée que la page du moteur : dsh:lwui:ollama:<model> ou dsh:lwui:plugin:<plugin>:<model>, avec chaque composant de fournisseur encodé en pourcentage. Une connexion DSH native locale facultative ajoute des choix dsh:native:<provider>:<model> issus du catalogue actif de cette instance. Ils réutilisent la configuration et les identifiants du fournisseur natif. Installez son package autonome Apache-2.0 depuis libre-webui/dsh-native-provider, ou préparez un bundle à partir de la distribution Libre WebUI. Les deux utilisent le nom de package @libre-webui/dsh-native-provider et conservent les clés de fournisseur dans DSH natif. La connexion exige le même hôte Unix et le même compte du système d’exploitation, avec un socket Unix privé ; elle ne peut pas isoler des applications partageant ce compte. Elle expose uniquement l’inférence des modèles, sans sessions d’agents natifs ni exécution native d’outils. Consultez le guide de configuration pour l’installation, les redémarrages de profil, les mises à niveau et la suppression. Une connexion ou un modèle sélectionné manquant provoque un échec sans changement de fournisseur. Les appels natifs apparaissent aussi dans Utilisation des fournisseurs avec le modèle choisi, les jetons signalés, la latence et le statut du résultat. Le profil de base dsh conserve le modèle par défaut de la composition active. Les compositions d’adaptateurs personnalisées exposent ce profil de base sans annoncer de remplacements de fournisseurs Libre WebUI non pris en charge.

Les titres et résumés de raisonnement résolvent une sélection DSH vers son fournisseur sous-jacent et effectuent une requête de texte directe sans outils ni session d’agent. Une requête au profil de base lit les valeurs par défaut du moteur actif, y compris les remplacements de l’entrée du pont, plutôt que de les déduire du catalogue actuel des fournisseurs. Les adaptateurs personnalisés nécessitent un modèle de tâche Ollama ou de plugin explicitement configuré pour ces fonctionnalités. Un fournisseur sélectionné indisponible entraîne l’échec normal ou un aperçu local du titre ; il ne provoque pas de requête vers un autre fournisseur.

La requête utilise les paramètres et identifiants de fournisseur de l’administrateur authentifié. Les identifiants d’un autre administrateur ne sont jamais sélectionnés implicitement. L’espace de travail Cordis configuré reste celui par défaut ; Chat ne lui substitue pas le répertoire personnel du compte serveur.

Work en bac à sable

Lorsque Cordis est activé, Work propose une commande Moteur distincte avec les choix Libre WebUI et DeepSeek Harness. Le sélecteur conserve les noms de modèles et identités de fournisseurs habituels. En interne, la sélection DSH est enregistrée sous dsh:<model> pour les fournisseurs LWUI. Les choix DSH natifs enregistrent plutôt providerType: dsh, l’identifiant exact du fournisseur natif et l’identifiant brut du modèle. Les contrôles habituels d’accès et de prise en charge des outils restent applicables ; l’utilisation d’identifiants natifs exige en plus un administrateur actif.

Chaque exécution crée une boucle d’agent DSH isolée en mémoire. Son adaptateur de modèle reçoit la transcription Work actuelle, les métadonnées du fournisseur, les images et les schémas des outils. Le corps de ses outils attend uniquement les résultats renvoyés par Work ; il ne peut ni lire les fichiers de l’hôte ni lancer ses processus.

Work reste chargé de valider les arguments, demander les autorisations, exécuter les outils dans l’environnement de l’espace de travail, enregistrer les résultats et l’état de relecture du fournisseur dans SQL, imposer les budgets et publier les événements. Un outil refusé produit le résultat de refus normal. L’annulation détruit DSH et suit le nettoyage habituel du conteneur Work. Après récupération du worker, un nouveau pilote DSH reçoit le contexte Work restauré et ne rejoue pas les effets d’outils déjà terminés.

L’intégration Work n’a pas besoin de la composition du moteur hôte ni du stockage de sessions JSONL. Elle suit les règles existantes de déploiement et d’exécution Docker/Kubernetes de Work, y compris les exigences de persistance partagée du mode équipe.

Limites de sécurité

La page du moteur et l’agent Chat côté hôte sont réservés aux administrateurs. Les sessions du moteur constituent une console administrateur partagée, y compris leurs invites système ; il ne s’agit pas d’espaces de travail individuels. Les comptes ordinaires ne peuvent ni les lire, ni les créer, ni les modifier, ni les annuler via l’API.

Les outils de système de fichiers de l’hôte fournis limitent les lectures et écritures à l’espace de travail configuré en utilisant les cibles canoniques du système de fichiers, avec résolution des liens symboliques. Les répertoires de travail spécifiques aux sessions doivent rester dans cet espace. La politique native de modification de DSH et les décisions d’autorisation ponctuelles du moteur restent applicables. Les plugins de composition installés par l’opérateur sont du code serveur de confiance et peuvent accorder des fonctionnalités supplémentaires. Les autorisations du moteur sont distinctes du flux d’autorisation et d’exécution en conteneur de Work.

Le pilote DSH de Work est distinct : il ne monte aucun plugin de système de fichiers, de shell ou de persistance de l’hôte et ne peut s’exécuter que via les mécanismes existants d’autorisation et de bac à sable de Work. Les fournisseurs de modèles distants restent facultatifs et utilisent la route de fournisseur configurée du compte sélectionné.