motore GraphRAG
Qualche settimana fa ho ricevuto una notifica su LinkedIn. Qualcuno aveva letto il mio articolo su ROE — il RAGraph Ontological Engine — e aveva deciso di trasformare quella visione in codice funzionante.
Il progetto si chiama TechRAG, è open source su GitHub, e nella sezione Note of Thanks c’è scritto:
Inspiration to this System is the Article: RAGraph Ontological Engine (ROE): Building a Hybrid Ontology-Driven Retrieval Engine for Technical Manuals — Andrea Belvedere
Non capita tutti i giorni di vedere la propria architettura prendere vita in un repository indipendente, con scelte tecnologiche diverse, un approccio pragmatico e una licenza MIT. Ho quindi deciso di analizzarlo a fondo, confrontarlo con la mia implementazione originale e raccontare cosa è rimasto fedele alla visione iniziale — e cosa invece è stato reinterpretato in modo sorprendente.
Da dove nasce tutto: la tesi di ROE
Nel mio articolo originale su ROE, la tesi centrale era chiara: un sistema RAG tradizionale recupera chunk semanticamente simili alla domanda, ma lascia al modello il compito più difficile — ricostruire da solo relazioni, cause, dipendenze e vincoli tra i concetti.
La soluzione che ho proposto era un motore ibrido a tre livelli:
- Ricerca vettoriale — Qdrant con embedding
bge-m3generati localmente via Ollama - Ontologia di dominio — un dizionario strutturato di concetti, generato a partire da un campione distribuito del documento
- Knowledge Graph — nodi, archi e fatti atomici con retro-riferimento alle fonti originali
L’idea era semplice nella sua ambizione: non passare al modello tutto il manuale, ma solo una vista compatta e pertinente fatta di pochi chunk, fatti atomici e percorsi del grafo. Meno rumore, più contesto utile, risposte verificabili.
Avevo implementato il tutto con un’architettura Docker-based (Qdrant + MongoDB), un’interfaccia web con chat ontologica e visualizzazione del grafo, e un sistema di audit con metriche di copertura e reasoning score.
TechRAG: la stessa visione, un’architettura completamente diversa
TechRAG prende la stessa tesi e la implementa con scelte architetturali quasi opposte alle mie. Ed è proprio questo il punto più interessante.
Database: da Qdrant+MongoDB a LanceDB+SQLite
Nel mio ROE, la persistenza era affidata a Qdrant per i vettori e MongoDB per documenti, ontologia e audit. Due servizi esterni, Docker, porte da esporre.
TechRAG fa una scelta radicalmente diversa: zero infrastruttura esterna. Usa LanceDB — un database vettoriale embedded, senza server, che scrive direttamente su filesystem — e SQLite via SQLAlchemy per tutto il resto. Documenti, chunk, entità, fatti atomici, percorsi semantici, ontologia serializzata: tutto in un unico file roe.db.
# ROE (originale)
Qdrant (vettori) + MongoDB (documenti/ontologia/audit)
→ Docker, porte 6333 e 27017
# TechRAG
LanceDB (vettori) + SQLite (tutto il resto)
→ Zero servizi esterni, un file .db
Questo cambia completamente l’esperienza di setup. Nel mio ROE serviva Docker attivo con entrambi i container; TechRAG si avvia con uv run roe ingest documento.pdf e funziona. È la differenza tra un’architettura pensata per un ambiente controllato e una pensata per funzionare ovunque, anche sul portatile di uno sviluppatore.
Knowledge Graph: da Neo4j a SQLGraph
Nel mio articolo accennavo all’uso di Neo4j per il knowledge graph, con MongoDB come alternativa più leggera per la persistenza documentale.
TechRAG introduce un approccio che non avevo considerato: SQLGraph. Invece di usare un database a grafo dedicato, modella nodi e archi come tabelle relazionali in SQLAlchemy. La tabella entities contiene i nodi (con nome, tipo ontologico, descrizione), la tabella facts contiene gli archi (con subject_id, predicate, object_id, source_text, confidence).
Non è un grafo nativo. Ma è un grafo perfettamente funzionante, interrogabile con semplici JOIN SQL, e con una caratteristica che Neo4j non ha: zero dipendenze infrastrutturali. Un BFS scritto in puro Python naviga le relazioni per costruire il contesto da passare al modello.
È la dimostrazione pratica che per molti casi d’uso — soprattutto in fase prototipale o su documenti di dimensioni contenute — un grafo su SQLite è più che sufficiente.
Modello linguistico: da 120B a 7B parametri
Qui la differenza è sostanziale. Nel mio ROE uso gpt-oss:120b-cloud, un modello da 120 miliardi di parametri. TechRAG usa qwen2.5:7b, un modello 17 volte più piccolo.
E funziona.
I smoke test riportati nel README parlano chiaro: con un PDF tecnico di 50 chunk, il sistema estrae 263 entità e 213 fatti atomici, risponde a domande con evidence_coverage=0.6, usando solo 4 chunk e 10 fatti nel contesto.
Questo è un dato importante. Suggerisce che la qualità del retrieval ibrido — la combinazione di similarità vettoriale e grafo ontologico — compensa ampiamente la minore potenza del modello. Il contesto è così ben selezionato che anche un LLM modesto può produrre risposte coerenti e verificabili.
Interfaccia: da Web UI a CLI
Il mio ROE espone una web app con chat, vista del grafo, Facts Composer per modificare i fatti, Audit Trail per lo storico delle interrogazioni.
TechRAG è puramente CLI:
uv run roe ingest manuale.pdf
uv run roe query "Quale procedura riduce il rischio associato all'allarme A1?"
uv run roe stats
Niente interfaccia grafica, niente visualizzazione del grafo. Solo terminale. Ma il comando query restituisce un JSON completo con answer e audit metadata (chunks usati, fatti, nodi, archi, percorsi, evidence_coverage, reasoning_score, recovered).
È un trade-off chiaro: meno fruibilità immediata, più integrabilità in pipeline automatiche.
Ciò che è rimasto identico (e non è poco)
Nonostante le differenze architetturali, il cuore dell’algoritmo è sorprendentemente fedele alla mia specifica originale.
La pipeline di ingestione
Entrambi i sistemi seguono lo stesso flusso:
- Parsing del documento (PDF via pypdf, Markdown, TXT)
- Chunking a 1000 caratteri con 200 di overlap
- Embedding via
bge-m3su endpoint Ollama - Ontology bootstrap su campione distribuito di chunk
- Estrazione per-chunk di fatti atomici con source back-reference
I prompt di estrazione sono diversi nella forma — TechRAG usa prompt in tedesco (l’autore è germanofono) — ma il concetto è identico: chiedere al modello di restituire triple JSON con subject, predicate, object e source.
I limiti di retrieval
Questo è il punto in cui TechRAG segue la mia specifica quasi alla lettera:
| Parametro | ROE (mio) | TechRAG |
|---|---|---|
| max_chunks | 4 | 4 |
| expand_threshold | 0.45 | 0.45 |
| expand_max_chunks | 8 | 8 |
| max_facts | 10 | 10 |
| max_nodes | 24 | 24 |
| max_edges | 18 | 18 |
| max_paths | 12 | 12 |
| min_coverage | 40% | 40% |
Sono gli stessi numeri. La stessa filosofia: partire da poco contesto, molto mirato, ed espandere solo se necessario.
L’adaptive query expansion
Quando il punteggio del miglior chunk è inferiore a 0.45, entrambi i sistemi non aumentano il contesto alla cieca. Riscrivono la query — TechRAG usa un prompt dedicato che chiede al modello di generare 2 query alternative con terminologia diversa — e ripetono il retrieval.
Recovery cycle
Se l’evidence coverage scende sotto il 40%, entrambi i sistemi attivano un secondo tentativo con più contesto. TechRAG implementa esattamente questo meccanismo, con max_recovery_rounds=1.
Dove TechRAG ha innovato
Ci sono anche alcune scelte che vanno oltre la mia specifica originale e che trovo particolarmente interessanti.
OpenAI-compatible abstraction
TechRAG non usa le API native di Ollama. Usa openai.OpenAI puntato all’endpoint /v1 di Ollama. Questo significa che lo stesso identico codice funziona con Ollama, OpenAI, vLLM o qualsiasi altro provider che esponga un endpoint OpenAI-compatibile.
# TechRAG clients.py
self._client = OpenAI(api_key=self.api_key, base_url=self.base_url)
resp = self._client.chat.completions.create(model=self.model, ...)
Il mio ROE è accoppiato a Ollama. TechRAG è provider-agnostico. È una differenza architetturale sottile ma potente: domani puoi passare a GPT-4o senza cambiare una riga di codice.
Pyproject.toml e uv
TechRAG è un pacchetto Python moderno con pyproject.toml, gestito con uv (il package manager di Astral, ordini di grandezza più veloce di pip). La struttura è pulita: roe/ come package, CLI tramite __main__.py, dipendenze dichiarate esplicitamente.
È un dettaglio che può sembrare minore, ma rende il progetto immediatamente riproducibile: uv sync e sei pronto.
Reasoning score euristica
Il mio ROE calcola il reasoning score basandosi sulla classificazione dei claim (Verified/Contradicted/Unsupported) fatta dal modello. TechRAG fa la stessa cosa ma aggiunge un’euristica interessante: cerca le keyword “verified”/”verifiziert”, “contradicted”/”widerspr”, “unsupported”/”nicht belegbar” direttamente nel testo della risposta con regex, senza una seconda chiamata al modello. È più rapido, anche se meno preciso.
BFS sul grafo SQL
La funzione graph_context_for in TechRAG implementa una vera BFS (Breadth-First Search) sul grafo SQL partendo dai seed entities estratti dai chunk recuperati. Visita nodi in ampiezza, raccoglie archi inbound e outbound, si ferma quando raggiunge max_nodes o max_edges. Non è un grafo nativo, ma il comportamento è esattamente quello di una graph traversal.
Normalizzazione L2 e cosine similarity su LanceDB
Un dettaglio tecnico che merita attenzione è la gestione della similarità in vector_store.py. TechRAG normalizza i vettori in L2 prima di inserirli e prima di ogni query, poi calcola la cosine similarity con un doppio fallback: prima assume distanza L2 quadratica (default Faiss), poi — se il valore esce dal range [-1, 1] — corregge con la formula per L2 lineare. In questo modo il score restituito è sempre una cosine similarity pulita tra 0 e 1, indipendentemente dall’indice usato da LanceDB sotto il cofano. È il tipo di dettaglio che fa la differenza tra un prototipo che funziona “a volte” e uno che funziona sempre.
Cosa significa tutto questo
Vedere la propria architettura reimplementata da uno sviluppatore indipendente è un’esperienza che consiglio a chiunque scriva di tecnologia.
La tesi è solida
Da una parte c’è la conferma che la tesi è solida. Se qualcuno legge il tuo articolo, ne comprende l’architettura, la reimplementa con stack diverso e ottiene risultati funzionanti, significa che l’idea non era legata a scelte implementative specifiche. Era un principio architetturale trasferibile. I tre livelli — vettoriale, ontologico, grafo — non dipendono da Qdrant o da Neo4j. Possono vivere in LanceDB e SQLite. L’importante è il principio.
La lezione sulla semplicità
Dall’altra c’è la lezione sulla semplicità. Il mio ROE usa Docker, Qdrant, MongoDB, una web app. TechRAG usa LanceDB, SQLite, un CLI. E funziona. Per molti use case — soprattutto prototipi, PoC, o integrazioni in pipeline CI/CD — la seconda via è oggettivamente migliore. Mi ha fatto riflettere su quanto spesso, come sviluppatori, tendiamo a sovra-ingegnerizzare l’infrastruttura quando il problema che stiamo risolvendo non la richiede.
Il modello non è tutto
Infine, c’è la questione del modello. Vedere qwen2.5:7b produce risultati comparabili a gpt-oss:120b-cloud quando il contesto è ben strutturato è una lezione importante: nel GraphRAG, la qualità del retrieval conta almeno quanto la potenza del modello. Forse di più. Un contesto ben selezionato — pochi chunk, fatti pertinenti, percorsi del grafo — permette anche a un LLM modesto di ragionare con precisione. Mentre un contesto rumoroso confonde anche il modello più potente.
Cosa avrei fatto diversamente
Se dovessi riscrivere ROE oggi, dopo aver studiato TechRAG, probabilmente:
- Adotterei l’astrazione OpenAI-compatible per rendere il motore provider-agnostico
- Valuterei LanceDB come alternativa embedded a Qdrant per abbassare la barriera di setup
- Mantenerei SQLite per la persistenza documentale nei casi d’uso più leggeri, riservando Neo4j solo quando la complessità del grafo lo giustifica davvero
- Terrei l’interfaccia web (resta fondamentale per esplorare il grafo e correggere i fatti), ma affiancherei una CLI per l’automazione
TechRAG non è “la versione migliore di ROE”. È una versione diversa, che risolve un problema diverso: accessibilità e velocità di setup. E in molti scenari, questa è esattamente la priorità giusta.
Conclusioni
Quando ho scritto l’articolo su ROE, l’obiettivo era condividere un’architettura. Non immaginavo che sarebbe diventata la base di un progetto open source indipendente, con licenza MIT, disponibile su GitHub per chiunque voglia sperimentare con il GraphRAG.
TechRAG dimostra che la visione era corretta: separare ontologia, knowledge graph e ricerca vettoriale; estrarre fatti atomici con retro-riferimento alle fonti; limitare il contesto a pochi elementi ben selezionati; verificare le risposte con metriche di copertura.
Dimostra anche che si può arrivare allo stesso risultato con stack completamente diversi — e che a volte la scelta più semplice (LanceDB invece di Qdrant, SQLite invece di MongoDB, qwen2.5 invece di un 120B) è quella giusta per cominciare.
Se volete provarlo, il repository è qui: github.com/FBR65/TechRAG.
E se state lavorando a qualcosa di simile partendo da un mio articolo, scrivetemi. Mi piacerebbe raccontare anche la vostra storia.
Riferimenti
- ROE — Motore GraphRAG locale con Ollama, Qdrant e Knowledge Graph
- TechRAG — RAGraph Ontological Engine (GitHub)
- SQLGraph — Knowledge Graph su SQLAlchemy (GitHub)
- Microsoft Research, From Local to Global: A GraphRAG Approach to Query-Focused Summarization
- Hogan A. et al., Knowledge Graphs, ACM Computing Surveys, 2020
AI Disclaimer
Questo articolo è stato sviluppato a partire dai miei appunti personali e dall’analisi del repository TechRAG, con l’ausilio dell’intelligenza artificiale per riorganizzare i contenuti e rendere più chiara la struttura.