Passa al contenuto principale

Bridge Cordis

Il bridge Cordis integra DeepSeek Harness (DSH) nel backend di Libre WebUI. DSH funziona come albero di plugin in un runtime Cordis ospitato da Libre WebUI: le funzionalità arrivano come servizi Cordis anziché moduli importati.

Il bridge è disattivato per impostazione predefinita. Quanto descritto qui avviene solo dopo l’abilitazione da parte dell’operatore; vedi Configurazione Cordis.

Perché un bridge anziché un’integrazione diretta

Importare i pacchetti DSH dai servizi Libre WebUI sarebbe più breve ma meno adatto. Renderebbe il motore una dipendenza in fase di compilazione: cambiare adattatore del modello, sostituire il ciclo agente o rimuovere il motore richiederebbe modificare e ridistribuire Libre WebUI.

Il bridge inverte la dipendenza. Libre WebUI usa un contratto astratto; un documento di composizione Cordis decide quale implementazione lo soddisfa:

  • Cambiare destinazione senza ricompilare. La composizione è YAML: scegliere un altro provider è una modifica di configurazione.
  • Configurare le funzionalità. Ogni funzionalità è una riga del Loader. Le modifiche alla composizione gestita dall’operatore valgono al successivo avvio dell’host.
  • Rimuovere senza residui. La fibra radice possiede servizi, listener ed effetti installati dal motore. Il suo rilascio li ritira tutti, permettendo di arrestare il motore senza riavviare Libre WebUI.

Livelli

Le dipendenze DSH concrete restano in backend/src/cordis/dsh/. Route e servizi applicativi usano i contratti del bridge. Il driver Work ha una composizione separata in memoria e non monta mai plugin del filesystem host.

Contratti

Il contratto si trova in backend/src/cordis/contracts.ts. È volutamente limitato ai dati necessari all’API Libre WebUI, senza terminologia interna del motore.

ContrattoScopo
DshEngine.status()Stato del ciclo di vita di ogni servizio (pending / ready / failed)
DshEngine.modelConfiguration()Modello e provider predefiniti della composizione attiva
DshEngine.listSessions()Riepiloghi delle sessioni, dalla più recente
DshEngine.getSession(id)Una sessione con i messaggi proiettati
DshEngine.createSession(opts)Riservare ID e directory di lavoro della sessione
DshEngine.updateSessionSettings(id, settings)Salvare il modello reale e la modalità di permessi nativi del filesystem quando inattiva
DshEngine.decideApproval(id, approvalId, decision)Risolvere un’approvazione nativa in attesa per la sessione proprietaria
DshEngine.deleteSession(id)Terminare una sessione e rilasciare l’agente
DshEngine.listAgents()Agenti attivi, distinti in radice o figli
DshEngine.listTools()Strumenti registrati dal motore e visibili al modello
DshEngine.sendMessage(id, txt)Avviare un turno e restituire un handle di streaming
DshEngine.cancel(id)Annullare il turno attivo della sessione

Il contratto viene pubblicato come servizio Cordis libreDshEngine: i consumatori lo leggono con ctx.get('libreDshEngine'), senza importare il modulo del bridge.

EngineStreamChunk trasporta text, reasoning, tool-call, tool-result, approval-request, approval-decision, error e done. I frame in tempo reale seguono l’agente e la sessione proprietari; il corrispondente messaggio persistente non viene emesso due volte. sendMessage restituisce un handle il cui subscribe riproduce quanto già emesso: un primo token veloce non si perde tra l’avvio del turno e il collegamento del listener HTTP.

Sequenza di un turno di chat

Si usa NDJSON anziché WebSocket perché un turno è una sequenza dal server al client dopo la richiesta. Mantenerla sul POST evita un secondo handshake, ticket e protocollo di riconnessione e conserva l’intero turno dentro una richiesta autenticata.

DONE e PENDING

Cordis attiva un plugin quando i servizi dichiarati sono disponibili; una riga attraversa quindi stati precedenti all’esecuzione. Distinguere i due concetti seguenti evita la causa più comune di un motore silenzioso.

Stato della voce Loader. Il Loader segue ogni riga attraverso PENDING → LOADING → ACTIVE oppure FAILED. Se mancano i servizi dichiarati, la riga resta in attesa indefinitamente anziché fallire: una composizione incompleta può quindi avviarsi senza servire nulla.

Disponibilità del servizio. L’host descrive ciascun servizio previsto così:

StatoSignificatoCausa
pendingNon registrato nel contestoLa riga che lo fornisce non è attiva oppure è disabilitata
readyRegistrato e utilizzabileLa riga che lo fornisce è attiva
failedDichiarato ma inutilizzabileSegnalato con una stringa detail

host.status() elenca disponibilità e servizi obbligatori mancanti; GET /api/cordis/health espone gli stessi dati. Omettere un servizio necessario genera un errore all’avvio, anziché pubblicare un motore che restituisce liste vuote.

Due catene di dipendenze sono facili da sbagliare:

  • dsh-tools non parte senza systemPrompt.
  • dsh-agent-loop parte solo quando esistono agents, sessions, llm, tools, systemPrompt e sessionProjections.

La mancanza di uno di questi servizi produce un archivio di sessioni funzionante ma nessuna risposta ai messaggi.

Configurazione del provider

La riga fornita libre-webui-llm-adapter serve i provider configurati in Libre WebUI. Il selettore della pagina Motore sceglie il modello per una sessione senza sostituire quella riga.

Le modifiche alle righe della composizione valgono al successivo avvio dell’host. Riavvia il backend oppure disabilita e riabilita Cordis se l’interruttore amministrativo è sbloccato. Le sessioni persistenti restano nell’archivio configurato e riprendono tramite la composizione attuale.

Il codice di integrazione fidato può usare direttamente le API del ciclo di vita del Loader. Il bridge non espone un endpoint per sostituire l’adattatore né ripristina automaticamente quello precedente se il nuovo fallisce.

Ripristino

Il rilascio della fibra radice dell’host rimuove tutto ciò che il motore ha installato. La garanzia dipende da questa singola relazione di proprietà:

  • I plugin registrano i servizi, ritirati insieme alla loro fibra.
  • Le sottoscrizioni session/event nascono nel costruttore del bridge e appartengono alla fibra della riga.
  • Il bridge traccia gli handle degli agenti e li dispone nel proprio effetto di rilascio.
  • L’host dispone il contesto radice, proprietario di tutte le righe.

stopCordisHost() è idempotente ed è collegato alla sequenza di arresto del backend: timer e handle di file vengono liberati esplicitamente, non lasciati all’uscita del processo.

Identità e persistenza delle sessioni

La pagina Motore riserva un ID opaco alla creazione. Con la persistenza attiva, l’intestazione viene salvata subito: anche una sessione vuota sopravvive al riavvio. Il bridge elenca sessioni salvate e attive, legge i log tramite l’API di persistenza convalidata di DSH e riprende l’agente sullo stesso ID per il seguito. I nuovi messaggi utente usano il costruttore DSH per messaggi identificati.

L’eliminazione annulla e dispone l’agente prima di rimuovere il file della sessione. L’adattatore JSONL locale verifica percorsi di archivio e sessione e rifiuta i collegamenti simbolici. Backend personalizzati senza supporto all’eliminazione restituiscono un errore, senza dichiarare falsamente la rimozione.

L’annullamento raggiunge agente nativo, richiesta al modello e lavoro degli strumenti. La disconnessione del client annulla il turno; i messaggi completati restano leggibili. Il buffer di riproduzione dello stream ha limiti e conserva le risposte veloci precedenti al collegamento del lettore.

Il Motore host è una funzione della modalità solo con una singola replica. Le distribuzioni team non possono montare il runtime JSONL locale. Work isolato usa invece gli archivi SQL esistenti per attività, esecuzioni, messaggi, approvazioni ed eventi.

Interfaccia HTTP

MetodoPercorsoScopo
GET/api/cordis/healthStato del bridge; senza autenticazione
GET/api/cordis/sessionsElencare le sessioni
POST/api/cordis/sessionsCreare una sessione
GET/api/cordis/sessions/:idLeggere una sessione e i messaggi
DELETE/api/cordis/sessions/:idTerminare una sessione
POST/api/cordis/sessions/:id/messagesInviare un messaggio con streaming NDJSON
POST/api/cordis/sessions/:id/cancelAnnullare il turno attivo
GET/api/cordis/agentsElencare gli agenti attivi
GET/api/cordis/toolsElencare gli strumenti registrati

Tutte le route tranne /health richiedono una sessione autenticata di amministratore e, quando il bridge non può servire richieste, rispondono 503 con code pari a CORDIS_DISABLED, CORDIS_STARTING o CORDIS_UNAVAILABLE.

Pagina Motore Cordis di Libre WebUI con elenco sessioni, strumenti registrati e trascrizione in streaming.

La pagina è frontend/src/pages/CordisPage.tsx, disponibile in /cordis dalla barra laterale. Elenca sessioni e strumenti, crea sessioni e aggiunge i turni in streaming alla trascrizione. Se il bridge è spento o non parte mostra il motivo invece di una lista vuota: altrimenti “nessuna sessione” e “nessun motore” sembrerebbero uguali.

Il client browser è frontend/src/utils/api/cordisApi.ts. Usa soltanto questa interfaccia, senza importare tipi backend o pacchetti @deepseek-ai/*, così il motore resta sostituibile senza modifiche frontend. I turni si consumano con sendMessage(sessionId, text, { onChunk }); il client analizza il JSON delimitato da nuove righe e gestisce i frammenti divisi fra letture di rete.

Controlli della chat del Motore

La pagina visualizza Markdown, tabelle e codice con evidenziazione sintattica, con comandi per copiare risposte e codice. Prompt di sistema e contesto runtime iniettato sono raccolti in Contesto della sessione, chiuso inizialmente, anziché apparire come messaggi dell’utente. Ragionamento esposto e attività degli strumenti hanno sezioni separate; i risultati restano associati alla corretta operazione dopo il ricaricamento.

Scegli un modello provider reale nel compositore. Il selettore usa i modelli locali e plugin disponibili all’amministratore connesso, con la relativa identità del provider. Le selezioni di persone e agenti di Chat non sono ID di modelli e non inseriscono le loro istruzioni nelle conversazioni del Motore. Le vecchie intestazioni errate persona-modello vengono ignorate come indicazioni predefinite, senza cambiare il log salvato.

Ogni sessione ha Sola lettura oppure Scrittura nello spazio di lavoro, applicato dalla politica filesystem DSH e dal confine canonico del bridge. Il compositore mostra l’area autorizzata. Le impostazioni sono eventi nativi persistenti e sopravvivono al riavvio; le modifiche durante un turno vengono rifiutate.

Una richiesta nativa di ampliamento dei permessi appare come scheda Consenti una volta / Rifiuta collegata all’operazione. L’approvazione vale solo per quella richiesta e non cambia i permessi permanenti. Non si possono approvare richieste scadute o annullate; Chat senza interfaccia rifiuta le domande che non può mostrare. Il bridge non offre accesso illimitato all’host.

Endpoint amministrativi aggiuntivi:

MetodoPercorsoScopo
GET/api/cordis/modelsModelli provider disponibili e valore predefinito del modello reale
PATCH/api/cordis/sessions/:id/settingsImpostare modello e/o modalità di permessi della sessione
POST/api/cordis/sessions/:id/approvals/:approvalIdDecidere una richiesta in attesa con allowed-once o rejected

Usare il motore in Chat

Abilita Accesso e criteri → Modelli di agenti CLI e Motore Cordis. Gli amministratori possono quindi scegliere DeepSeek Harness in Chat. Ogni richiesta riceve una sessione motore transitoria nuova contenente la trascrizione fornita da quella richiesta. Il normale database Chat resta autorevole: conversazioni indipendenti, fork e tentativi ripetuti non condividono una cronologia motore invisibile. Il log transitorio viene eliminato alla conclusione o all’annullamento e non appare nella pagina Motore.

La composizione provider standard elenca anche DeepSeek Harness · modello (provider) nel gruppo Agenti. Gli ID salvati racchiudono la stessa route qualificata della pagina Motore: dsh:lwui:ollama:<model> o dsh:lwui:plugin:<plugin>:<model>, con ogni componente provider codificato percentualmente. La connessione DSH nativa locale opzionale aggiunge voci dsh:native:<provider>:<model> dal catalogo attuale dell’istanza, riutilizzandone configurazione e credenziali.

Installa il pacchetto autonomo Apache-2.0 da libre-webui/dsh-native-provider, oppure prepara un bundle dalla distribuzione Libre WebUI. Entrambi usano @libre-webui/dsh-native-provider e conservano le chiavi in DSH. Servono lo stesso host Unix e account del sistema operativo, con socket Unix privato; la connessione non isola applicazioni dello stesso account. Espone soltanto inferenza, senza sessioni agente o esecuzione strumenti nativi. La guida alla configurazione descrive installazione, riavvii dei profili, aggiornamenti e rimozione. Connessioni o modelli mancanti falliscono senza cambiare provider. Le chiamate native compaiono anche in Utilizzo provider con modello, token dichiarati, latenza ed esito.

Il profilo base dsh mantiene il modello predefinito della composizione attiva. Le composizioni con adattatori personalizzati espongono questo profilo senza pubblicizzare sostituzioni provider LWUI non supportate.

Titoli e riepiloghi del ragionamento risolvono la selezione DSH nel provider sottostante e inviano testo direttamente, senza strumenti o sessione agente. Il profilo base legge i valori della composizione attiva, incluse le sostituzioni della riga bridge, senza dedurli dal catalogo. Gli adattatori personalizzati richiedono un modello per le attività Ollama o plugin configurato esplicitamente. Un provider indisponibile produce il normale errore o l’anteprima locale del titolo, senza inviare richieste a un altro provider.

Si usano impostazioni e credenziali del provider dell’amministratore autenticato; quelle di altri amministratori non vengono scelte implicitamente. L’area Cordis configurata resta quella predefinita; Chat non la sostituisce con la home dell’utente server.

Work in sandbox

Quando Cordis è attivo, Work offre un controllo Motore separato con Libre WebUI e DeepSeek Harness. Il selettore conserva nomi e identità dei provider. Internamente, DSH è salvato come dsh:<model> per provider LWUI; le scelte native salvano invece providerType: dsh, ID provider nativo esatto e ID modello grezzo. Restano validi i controlli di accesso e capacità strumenti; le credenziali native richiedono inoltre un amministratore attivo.

Ogni esecuzione crea un ciclo DSH isolato in memoria. L’adattatore riceve trascrizione Work attuale, metadati del provider, immagini e schemi strumenti. I corpi degli strumenti attendono soltanto i risultati restituiti da Work: non possono leggere file host né avviare processi host.

Work resta responsabile di convalidare argomenti, chiedere approvazioni, eseguire nel runtime dell’area di lavoro, salvare risultati e stato di riproduzione provider in SQL, applicare budget e pubblicare eventi. Uno strumento negato produce il risultato normale di rifiuto. L’annullamento dispone DSH e segue la pulizia del container Work. Dopo il recupero del worker, un nuovo driver riceve il contesto ripristinato senza ripetere gli effetti già completati.

L’integrazione non richiede composizione del Motore host o archivio JSONL. Segue le regole Docker/Kubernetes e di distribuzione Work, inclusa la persistenza condivisa della modalità team.

Confine di sicurezza

Pagina Motore e agente Chat lato host sono riservati agli amministratori. Le sessioni sono una console amministrativa condivisa, inclusi i prompt di sistema, non un’area per utente. Gli account ordinari non possono leggerle, crearle, modificarle o annullarle tramite API.

Gli strumenti filesystem forniti confinano letture e scritture all’area configurata tramite percorsi canonici, risolvendo anche i collegamenti simbolici. Le directory di sessione alternative devono restare in quest’area. Restano valide la politica DSH sulle modifiche e le approvazioni singole del Motore. I plugin di composizione installati dall’operatore sono codice server fidato e possono concedere capacità aggiuntive. Le approvazioni del Motore sono separate da quelle di Work e dalla sua esecuzione nei container.

Il driver DSH di Work è distinto: non monta plugin filesystem, shell o persistenza host ed esegue solo attraverso autorizzazione e sandbox Work. I provider remoti restano opzionali e usano la route configurata dell’account selezionato.