Architettura RAG

Come RAG
è davvero cablato.

Un'architettura a separazione delle responsabilità. Il controller fa solo routing; il modello di embedding risiede sul demone di inferenza nativo; il vector store risiede sul data worker. Tre processi, tre responsabilità, un solo cavo. Questa pagina è la vista tecnica per power user e operatori che vogliono sapere perché ha questa forma e come piegarla quando serve.


La catena

Controller → Inferenced → Data Worker.

Su una richiesta di chat che attiva RAG, tre processi partecipano in sequenza:

  1. Controller (porta 8880) — riceve la richiesta, la instrada. Il percorso RAG attraversa due route: /api/v1/embeddings (proxy verso Inferenced) e /api/v1/vector/search (aggregatore sui data worker). Il controller non fa inferenza in-process; è tutto routing.
  2. Inferenced (porta 8883) — il runtime GGUF nativo. Tiene un modello di embedding quantizzato (nomic-embed-text-Q4_K_M.gguf, 768 dimensioni, ~80 MB) caricato in memoria. La route embeddings del controller fa da proxy qui.
  3. Data Worker (porta 8892) — archivia i documenti indicizzati e le voci vettoriali. L'aggregatore vector-search del controller fa l'embedding della query (tramite #2), poi chiede al data worker gli hit k-nearest-neighbour.

La richiesta di chat completion attraversa poi il percorso di inferenza con i passaggi recuperati iniettati come contesto, restituendo al client la risposta ancorata + i metadata di citazione.


Perché questa forma

Tre ragioni.

1. Il controller resta solo-routing.

Se il controller facesse l'embedding in-process, ogni nodo che esegue un controller dovrebbe avere il modello di embedding caricato — divorando RAM e consumando tempo di avvio. Peggio: in configurazioni cluster il controller potrebbe non essere il nodo con GPU, quindi farebbe embedding su CPU mentre una GPU perfettamente buona sta a un host di distanza. Mantenere il controller un router puro significa che scalare il lavoro di embedding è disaccoppiato dallo scalare il lavoro di routing.

2. Il modello di embedding è GGUF + piccolo.

nomic-embed-text-Q4_K_M è 80 MB su disco, gira comodamente su CPU e produce vettori a 768 dimensioni compatibili con il layout di storage del data worker. Quantizzare a Q4_K_M scambia una piccola quantità di precisione di embedding-quality per un modello che entra in memoria su un Raspberry Pi 4 e fa l'embedding di 1k token ben sotto il secondo su qualsiasi CPU moderna. GGUF significa che llama.cpp può servirlo direttamente; Inferenced parla già GGUF, quindi è il posto naturale dove ospitarlo.

3. Il vector store sta dove vivono i dati.

Il data worker contiene già i documenti sorgente, i chunk, i confini di tenant, il ledger di audit e lo storage dei file. Mettere le voci vettoriali accanto ai chunk (invece che in un vector database separato) significa che cancellazione, ri-embedding, isolamento di tenant e backup sono tutte una sola operazione, non cinque.


Topologie personalizzate

Puntare il controller a un backend di embedding diverso.

Il default mette Inferenced e il controller sullo stesso host (o ovunque il controller possa raggiungere Inferenced sulla rete del cluster). Alcuni deployment vogliono qualcosa di diverso — Inferenced su un host GPU dedicato, un servizio di embedding esterno, o un modello GGUF del tutto diverso. Si gestisce con una variabile d'ambiente sul controller:

# /etc/eldric/eldric-aios.env

ELDRIC_EMBED_BACKEND_URL=http://inferenced-host:8883

Qualunque URL che parli l'endpoint OpenAI-compatibile /v1/embeddings funziona — Inferenced è il default, ma anche Ollama lo soddisfa, così come un server llama.cpp self-hosted, vLLM, TGI, o qualsiasi API cloud di embedding se vuoi spedire gli embedding fuori sede (la maggior parte dei clienti non vuole).

Riavvia il controller dopo aver modificato il file env (sudo systemctl restart eldric-aios-controller) e il nuovo backend viene preso dalla richiesta di chat successiva.


Nodo singolo vs cluster

Dove risiede ciascun processo.

Nodo singolo

Tutto su un solo host. Il controller, Inferenced e il data worker sono tre unità systemd sulla stessa macchina. Inferenced coabita con il lavoro di inferenza LLM; l'embedding occupa una frazione della memoria GPU e gira accanto a modelli più grandi senza contesa.

Multi-nodo

Il controller risiede su un piccolo host di gestione. Inferenced risiede su un host GPU (o su un qualsiasi nodo con CPU sufficiente se non vuoi dedicare GPU all'embedding). Il data worker risiede sull'host con storage abbondante. Il controller raggiunge Inferenced tramite ELDRIC_EMBED_BACKEND_URL; il controller raggiunge i data worker tramite il layer di topology discovery (nessuna env var necessaria — pushed dall'heartbeat).

Edge

Su un Pi 4 o NUC che esegue il runtime edge minimale, tutti e tre risiedono sul singolo host edge (la piattaforma li distribuisce come unico bundle). Il nodo edge fa embedding in locale, indicizza in locale, interroga in locale. Se è configurato un cluster centrale, il percorso di esportazione/importazione bundle sposta intere basi di conoscenza tra edge e centro senza ri-embedding.


Modalità di guasto

Cosa succede quando qualcosa è giù.

Se Inferenced non è raggiungibile, il controller restituisce un errore strutturato sulla route degli embeddings (502 con corpo esplicativo). I nuovi upload non riescono ad andare in embedding e restano marcati "in coda"; la piattaforma riprova al ciclo di upload successivo una volta che Inferenced è tornato. Le query di chat che avrebbero attivato RAG ricadono sulla chat semplice (senza citazioni) invece di far fallire l'intera richiesta.

Se il data worker non è raggiungibile, vector-search restituisce 503; le richieste di chat ricadono sulla chat semplice. L'upload tramite il percorso chunked-upload dell'interfaccia si accoda sul controller e si scarica una volta che il data worker è tornato.

Se il modello di embedding non è caricato in Inferenced, la route degli embeddings restituisce 404 con un suggerimento model-not-loaded. La dashboard amministrativa di Inferenced ha un pulsante Carica; cliccalo (il file risiede in /data/eldric/models/nomic-embed-text-Q4_K_M.gguf in un'installazione standard) e l'embedding riparte alla richiesta successiva.


Andare oltre

Avanti.

Per la guida pratica lato cliente: usare RAG. Per l'anteprima della memoria compressa che velocizza la ricerca vettoriale in concorrenza: retrieval avanzato. Per l'anteprima lato inferenza che consulta la memoria al confine del prompt invece di fare round-trip: inferenza con memoria intelligente.

Per il resto del sistema in cui si inserisce il percorso RAG: come funziona percorre l'intera architettura a 4 livelli (Client → Edge → Controller / Router / Data → 10 worker, Inferenced incluso).