DatasetForge
Micro-gestionale FastAPI per la curazione e validazione del dataset di fine-tuning LLM Nutriverso. Editor collaborativo, flag system avanzato, grafo di navigazione interattivo e pipeline di validazione incrociata.
Cosa fa DatasetForge?
- Dataset editing β CRUD entry JSONL, validazione mojibake/code-leak, export
- Flag System V3 β 11 tipi di flag, workflow biforcato (umano + auto), anti-flooding
- Graph Layer β endpoint per navigare il grafo del sito (path finder, neighbors, search)
- Cross-validation β pipeline 5-stadi che verifica coerenza dataset β grafo
- Graph Mapper β crawler per espandere/verificare il grafo (bundle statico + crawl dinamico)
- Backup & health β backup atomici crash-safe, health score dataset + grafo
Stack tecnologico
| Livello | Tecnologia |
|---|---|
| Backend | FastAPI + uvicorn + pydantic v2 |
| Frontend | HTML + CSS + JavaScript vanilla (no framework, no build step) |
| Grafo | cytoscape.js per la visualizzazione interattiva |
| Validazione testo | pyahocorasick (gazetteer) + rapidfuzz (fuzzy) |
| Semantica (opt.) | sentence-transformers (LaBSE/MiniLM multilingue) + faiss-cpu |
| Deploy | Container Docker 100% replicabile |
Avvio rapido
cd tools/datasetforge
pip install -r requirements.txt
python3 server.py # β http://localhost:8420
Come iniziare
1. Avvia il server
DatasetForge gira su http://127.0.0.1:8420 di default.
python3 server.py
# β
Server running at http://127.0.0.1:8420
2. Verifica che sia attivo
curl http://127.0.0.1:8420/api/health
# β {"status":"ok","entries":4926,"version":"2.1"}
3. Apri l'interfaccia web
Naviga su http://127.0.0.1:8420/ β vedrai la tabella delle entry del dataset con filtri, ricerca e strumenti.
Autenticazione & API Key
Ogni chiamata API (eccetto /api/health, le pagine web e
/docs) richiede l'header X-API-Key. Le chiavi sono
mappate in keys.json con un agent_id e un
role.
| Ruolo | Cosa puΓ² fare |
|---|---|
| reader | Solo GET: leggere dataset, flag, grafo, changelog |
| writer | + write non distruttive: creare/modificare entry e flag, rispondere, crossval |
| admin | + operazioni distruttive: delete, restore backup, apply-fix, revert, resolve |
Usare una API Key
# Health (pubblico, no key)
curl http://127.0.0.1:8420/api/health
# Autenticato (reader)
curl -H "X-API-Key: YOUR-KEY" http://127.0.0.1:8420/api/stats
# Creare una entry (writer)
curl -X POST -H "X-API-Key: YOUR-KEY" \
-H "Content-Type: application/json" \
http://127.0.0.1:8420/api/entries \
-d '{"messages":[...],"lang":"it"}'
Nella web UI: l'API key viene richiesta al primo accesso se
l'autenticazione Γ¨ attiva. Viene salvata in localStorage e
inviata automaticamente ad ogni richiesta. Se la modalitΓ Γ¨
"open" (auth disabilitata), l'accesso Γ¨ diretto in read-only.
FunzionalitΓ implementate
Le funzionalitΓ dell'interfaccia sono definite da user stories con criteri di accettazione espliciti.
variant-of-{id}.
dataset_export_YYYYMMDD_HHMMSS.jsonl pronto per il training.
POST /api/bulk e /api/bulk/create per Claude agent. Rate limit 200 modifiche/richiesta, validazione atomica (tutto o niente), auto-backup.
changelog.jsonl. UI con tabella, filtri (user, data, azione), revert (solo admin).
API Reference
La documentazione interattiva completa di tutti gli endpoint Γ¨ disponibile sulla pagina dedicata, che fetcha lo schema OpenAPI a runtime e lo renderizza dinamicamente con filtri per tag, metodo e ricerca testuale.
Gruppi di endpoint principali
| Router | Prefix | Descrizione |
|---|---|---|
entries.py | /api/entries/* | CRUD entry, bulk, export, validate |
flags.py | /api/flags/* | Flag CRUD, resolve, dedup, import/export, auto-flag |
graph.py | /api/graph/* | Grafo: screens, neighbors, paths, search, scan, diff |
crossval.py | /api/crossval/* | Cross-validation pipeline run + results |
maintenance.py | /api/* | Backups, health-score, changelog, stats |
Architettura
DatasetForge segue un'architettura a 3 strati: Managers (logica di dominio + stato) β Routers (endpoint HTTP thin) β LockHub (concorrenza e write-safety).
Principio chiave: i router sono thin β validano input e delegano ai manager. Nessuna logica di dominio nei router. I manager sono l'unico punto di accesso a stato e file.
Managers & Routers
Managers (logica di dominio)
I manager contengono tutta la logica di business e gestiscono lo stato persistente:
| Manager | File | ResponsabilitΓ |
|---|---|---|
| DatasetManager | dataset_manager.py | CRUD entries JSONL, backup atomici, validazione, health-score |
| FlagManager | flag_manager.py | Flag CRUD, resolve/reject/wontfix, dedup_key anti-flooding, import/export |
| GraphManager | graph_manager.py | Caricamento kb.json, path finder (BFS), scanner integration, diff/apply |
| CrossValManager | crossval_manager.py | Pipeline 5-stadi (ingest β extract β disambig β path-check β report) |
Routers (endpoint HTTP)
Ogni router Γ¨ creato via factory create_router() e riceve i manager come dipendenze. I router sono stateless: non mantengono dati, solo validano e delegano.
# routers/entries.py β pattern
def create_router(dataset_manager, flag_manager, hub):
router = APIRouter(prefix="/api/entries", tags=["entries"])
@router.get("")
async def list_entries(...):
return dataset_manager.list(...)
@router.put("/{entry_id}")
async def update_entry(entry_id, body, actor):
return dataset_manager.update(entry_id, body, actor)
return router
LockHub & write-safety
LockHub Γ¨ il single source of truth per tutti i
lock del sistema. Centralizza la concorrenza per evitare deadlock e garantire
write-safety cross-file.
class LockHub:
"""Single source of truth per i lock."""
dataset_lock = threading.RLock() # dataset.jsonl
flags_lock = threading.RLock() # flags.json
graph_lock = threading.RLock() # kb.json
_global_lock = threading.RLock() # cross-file transactions
Regola deadlock-free: acquisisci sempre i lock nell'ordine
_global_lock β file-specific. Mai lock annidati cross-manager.
Write-safety (crash-safe)
Tutte le scritture passano per atomic_write_* con:
fsyncsu file + directoryos.replace(atomic rename, crash-safe)- Single-writer cross-process garantito da
file_lock(RLock intra-processo +flocka livello OS)
Cross-val snapshot + MVCC
La cross-validation usa un pattern snapshot per non bloccare il sistema durante il compute lungo:
def run_crossval():
# Snapshot breve (millisecondi)
with hub.dataset_lock, hub.graph_lock:
ds_snapshot = deepcopy(dataset_manager._load_all())
kb_snapshot = deepcopy(graph_manager._load_kb())
# Compute lungo (secondi/minuti) β lock rilasciati
results = pipeline(ds_snapshot, kb_snapshot)
# Write flag breve (atomico)
with hub.flags_lock:
for flag in results:
flag_manager._create_or_update(flag)
Flag System V3
Il Flag System Γ¨ il cuore della curazione collaborativa. Permette di segnalare problemi sul dataset e sul grafo, tracciarli in un workflow strutturato e risolverli β con anti-flooding per i flag generati automaticamente.
11 tipi di flag
| Tipo | Scope | Source tipico |
|---|---|---|
correction | dataset | human/system β mojibake, empty_field, wrong_value |
question | dataset | human |
gap | dataset | human β missing_faq, missing_topic |
duplicate | dataset | human/system β exact_hash, near_dup |
contradiction | cross | human/validator |
quality | dataset | human/system β too_short, unclear |
language | dataset | human β lang_mismatch |
safety | dataset | human/system β code_leak, pii |
note | free | human |
graph_gap | graph | crawler/human β orphan_screen, missing_edge |
graph_conflict | cross | validator/human β unreachable_path |
crawl_anomaly | crawl | crawler β blank_page, demo_blocked |
Workflow biforcato
I flag umani e quelli automatici seguono percorsi separati:
Stati chiave:
auto_pendingβ generato automaticamente, non in coda operatore attivatriagedβ un umano ha valutato e deciso il da farsisuppressedβ deduplicato/sovrascritto da run successivowontfixβ il flag Γ¨ valido ma la condizione Γ¨ accettabile (chiusura permanente anti-reapertura)
Anti-flooding (3 meccanismi)
dedup_keyβ hash ditype + targets + sub_type. Prima di creare: se open β aggiorna; se resolved/wontfix β skip- Rate limit β max N flag aperti per
(source, type). Oltre β coalesce in batch - Coalescing β 28 schermate orfane identiche β 1 flag con 28 graph_targets
Auto-priority deterministica
La prioritΓ dipende dal contenuto (evidence/verdict), non dalla fonte:
| Condizione | Auto-priority |
|---|---|
| graph_conflict con verdict UNREACHABLE | P0 |
| safety con leak confermato | P0 |
| graph_gap schermata orfana | P1 |
| graph_conflict con verdict ABBREVIATED_VALID | P2 |
| crawl_anomaly transiente (403/timeout) | P3 |
| confidence < 0.7 | abbassa di 1 livello |
Graph Mapper
Il Graph Mapper Γ¨ il crawler che mappa automaticamente il grafo di navigazione
del sito, espande e verifica kb.json. Risolve il problema delle
schermate orfane, edge mancanti e dati stale.
Fase 1 β Analisi statica del bundle
Input: main.bundle.js (giΓ scaricato). Deterministica, no auth.
- Route table β tutti i
<Route path> - Navigation calls β
history.push(),<Link to> - Action labels β i18n keys IT/EN/FR/ES
- Dialog triggers β
setDialogVisible(true)
< 10s Veloce, deterministico, non serve il sito online.
Fase 2 β Crawling dinamico
Input: credenziali demo + route dalla Fase 1. Usa Playwright.
- Login automatico β JWT + cookie salvati
- Per ogni route: estrai elementi cliccabili, click, registra edge
- Rate limiting: 0.5s tra click, max 2 concurrent
- Stop su 5xx o 429
5-10 min Cattura la realtΓ : cosa Γ¨ visibile, cosa fa ogni bottone.
Fase 3 β Diff & auto-fix
Confronta kb.json con i risultati delle fasi 1-2:
| Check | Cosa rileva |
|---|---|
| Schermate mancanti | Route nel bundle non in kb.json |
| Schermate orfane | Screen senza edge in/out |
| Edge mancanti | Navigazione osservata non in kb.json |
| Edge errati | Edge in kb.json non confermati |
| Trigger mancanti | Edge senza testo del bottone |
Apply mode con dry_run=true|false: preview o applica fix con backup automatico.
Path Finder
L'endpoint /api/graph/paths risolve il caso d'uso tipico:
"Dal login come arrivo a inserire patologie?" β calcola tutti i cammini
possibili da A a B con step descritti in linguaggio naturale.
Cross-Validation
La validazione incrociata rileva automaticamente le risposte nel dataset che descrivono path di navigazione non validi rispetto al grafo reale del software, collegando le discrepanze al Flag System.
Il problema in una frase: l'assistant dice "vai in Elenco pazienti β seleziona β Visita β tab Patologie", ma nel grafo reale il tab "Patologie" non esiste, oppure l'edge Γ¨ mancante. Il dataset insegna all'LLM percorsi sbagliati.
Pipeline a 5 stadi
Stadio 1 β Ingest
Costruisce il gazetteer multilingue (surface forms normalizzate per ogni entitΓ in 4 lingue) e la matrice di reachability (tutte le coppie srcβdst raggiungibili β lookup O(1) con 64 nodi = 4096 entry).
Stadio 2 β Extract (3 vie combinate)
- Aho-Corasick β match esatti e quasi-esatti in O(|testo|), 4 lingue gratis
- Fuzzy (RapidFuzz) β parafrasi e typo, soglie differenziate (L1: 88, L2: 85, L3: 82)
- Semantic (opz.) β LaBSE/MiniLM multilingue per parafrasi cross-lingua
Stadio 3 β Disambiguazione
Context anchoring: una menzione L2/L3 (es. bottone "Nuovo") si risolve solo fra le entitΓ figlie della schermata corrente nel path ricostruito. Risolve la polisemia senza modelli pesanti.
Stadio 4 β Path-check
Verifica che il path dichiarato sia percorribile nel grafo:
| Step | Condizione di validitΓ | Se fallisce |
|---|---|---|
| NAVIGATE (AβB) | edge AβB nella reachability | unreachable_edge |
| ACT (azione X) | X β actions(S) | missing_action |
| SELECT | select ha target valido | invalid_selection |
Tolleranza salto implicito: distingue path validi ma abbreviati
(direct edge mancante ma reachability OK β info) da path
completamente rotti (nessun cammino β error).
Questo previene il falso positivo piΓΉ frequente.
Stadio 5 β Report & Flag
Ogni break con severity error/warning genera un flag automatico (source: "validator", stato auto_pending, con dedup_key anti-flooding). Output: metriche di copertura, qualitΓ (valid/broken ratio), top path, breakdown per lingua.
Verdict possibili
| Verdict | Significato |
|---|---|
| VALID | Path completamente percorribile |
| ABBREVIATED_VALID | Path valido ma con salti impliciti (non Γ¨ un errore) |
| BROKEN | Path impossibile, nessun cammino |
| NO_PATH_FOUND | Riferimenti estratti ma path non ricostruibile |
| NO_REFERENCES | Nessun riferimento nav (entry non-nav, esclusa) |
Feedback bidirezionale
Il sistema Γ¨ un ciclo in entrambe le direzioni:
- Grafo β Dataset: valida il dataset (caso principale)
- Dataset β Grafo: menzioni ad alta frequenza senza match β alias/entitΓ mancanti in
kb.jsonβ flagnew_alias_candidate
Deployment
DatasetForge Γ¨ deployato come container Docker 100% replicabile: stessa immagine su qualsiasi host Linux AMD64, dati su volumi, API key come secret.
Stack di dipendenze
# requirements.txt (core)
fastapi
uvicorn
pydantic>=2
pyahocorasick
rapidfuzz
# requirements-optional.txt
sentence-transformers # ~500MB, lazy import
faiss-cpu # ~100MB, optional NN search
La cross-validation funziona con sole dipendenze core (gazetteer + fuzzy). Il semantic matcher Γ¨ opzionale con fallback automatico.
Deploy con Docker
cd tools/datasetforge/deploy
# 1. API keys (secret)
mkdir -p secrets
cp ../keys.json.example secrets/keys.json
$EDITOR secrets/keys.json # genera chiavi ad alta entropia
# 2. Dati (volume)
docker volume create datasetforge-data
docker run --rm -v datasetforge-data:/data -v "$PWD":/src busybox \
sh -c "cp /src/dataset.jsonl /src/kb.json /data/ && \
mkdir -p /data/backups && chown -R 1000:1000 /data"
# 3. Avvio
cp .env.example .env # opzionale: personalizza porta/auth
docker compose up -d --build
# 4. Verifica
curl http://127.0.0.1:8420/api/health
# β {"status":"ok","entries":4926,"version":"2.1"}
Nota: il container gira non-root (uid 1000). Il
chown -R 1000:1000 sul volume Γ¨ necessario perchΓ© possa scrivere i
backup. keys.json non va mai committato (.gitignore lo esclude).
Configurazione
| Variabile | Default | Descrizione |
|---|---|---|
DF_HOST | 127.0.0.1 | Bind address |
DF_AUTH_ENABLED | true | Abilita/disabilita autenticazione |
DF_API_KEYS_PATH | keys.json | Percorso file chiavi |
Struttura del progetto
tools/datasetforge/
βββ server.py # Entrypoint: app, CORS, router includes
βββ config.py # Path, constants, VALID_* sets
βββ models.py # Pydantic BaseModel (entry + flag + graph)
βββ validators.py # validate_entry_text, _validate_flag_*
βββ managers/ # Logica di dominio + LockHub
βββ routers/ # Endpoint HTTP thin
βββ graph_mapper/ # Crawler (phase1/2/3 + runner)
βββ web/ # UI vanilla JS + CSS
β βββ index.html
β βββ docs.html # β questa pagina
β βββ api_docs.html # API reference interattivo
β βββ style.css
β βββ js/ # entries, flags, graph, groups, coverage, app
βββ tests/ # pytest (entries, flags, graph, crossval, maintenance)
βββ docs/ # Documentazione architetturale
βββ deploy/ # Docker setup