APAndrea Pellizzari
Tutti i lavori
AI / Chatbot tecnico

BlumCat: chatbot tecnico interno su catalogo ferramenta da mobili — gate logic deterministico, eval set come rete di sicurezza, single source of truth MD→Python→prompt

Un assistente tecnico interno per chi vende e installa ferramenta Blum (cassetti LEGRABOX/MERIVOBOX, cerniere CLIP top, ante a ribalta AVENTOS): copre 45 famiglie, 1.936 codici articolo verificabili e un manuale tecnico di 758 pagine. Costruito su pattern proprietari di context engineering — push deterministico del contesto invece di tool che il modello potrebbe dimenticarsi di chiamare, gate logic server-side con marker espliciti, tool_choice forzato API-side per i punti ad alto rischio, single source of truth con derivazione automatica MD→Python→system prompt — e gated da un eval set deterministico che fa da rete di sicurezza per i refactor. In produzione LAN dal 2026, costo ~€0,05–0,15 per conversazione.

PythonFastAPIAnthropic Claude Haiku 4.5 + Sonnet 4.6sentence-transformers (mpnet 768d)MinerUpdfplumber+3
Cliente
PMI manifatturiera italiana, settore ferramenta da mobili
Ruolo
Architettura, pipeline OCR doppia, agente tool-calling, gate logic, eval set, deploy produzione
Durata
Sviluppo continuo da metà 2025, pattern stabilizzato a fine aprile 2026
Anno
2026
Stack tecnico
PythonFastAPIAnthropic Claude Haiku 4.5 + Sonnet 4.6sentence-transformers (mpnet 768d)MinerUpdfplumberSQLite + embedding BLOBNSSM Windows serviceprompt caching ephemeral

Contesto

Cliente PMI nel settore della ferramenta da mobili, rivenditore tecnico di un grande fornitore austriaco (Blum) con un catalogo molto strutturato: cassetti modulari, cerniere a clip, sistemi a ribalta, suddivisione interna, guide di scorrimento. Il manuale ufficiale è un PDF di 758 pagine fitte di tabelle, formule grafiche e disegni tecnici quotati; le famiglie prodotto sono 45, i codici articolo quasi duemila, e i nomi commerciali (LEGRABOX, MERIVOBOX, AVENTOS HK-S, CLIP top BLUMOTION) si riferiscono a sistemi che si configurano combinando 4–6 componenti.

Il sapere viveva nelle teste di pochi tecnici esperti. Le domande che si ripetevano ogni giorno — "cassetto da 50 cm in cucina, che ferramenta serve?", "questa cerniera mi regge un'anta da 12 kg?", "in TANDEMBOX la sigla D è una spondina o no?" — passavano per telefonate o email, e un commerciale junior impiegava mesi a diventare autonomo. Il manuale era consultato poco perché lungo da scorrere e organizzato per logica del fornitore, non dell'utente.

L'idea iniziale era "un ChatGPT che sa di Blum". Il problema vero era diverso: garantire che il bot non inventasse mai un codice, e che le scelte di configurazione passassero per gli stessi vincoli del manuale (portate, NL ammesse, abbinamenti consentiti) — perché un dato sbagliato in distinta significa un ordine sbagliato a fornitore, non un'esperienza utente sub-ottimale.

Sfida

Tre garanzie che un RAG generico su PDF non offre out-of-the-box:

  1. Zero allucinazioni sui codici: pattern come 770M5002S o 753.5001S sono linguistico-statisticamente plausibili anche quando inventati. Servono meccanismi che impediscano al modello di "tirare a indovinare".
  2. Chiusura del flusso conversazionale sui dati chiave: un cassetto si configura partendo da profondità + portata; senza quei due dati la risposta è inventabile. Il bot non deve mai assumere default silenziosi su parametri che cambiano il codice principale.
  3. Refactor sicuri nel tempo: il sistema cresce, il manuale cambia ogni anno, le regole vengono raffinate dal tecnico interno del cliente. Serve un'infrastruttura che permetta di mettere mano al codice senza accorgersi mesi dopo che una piccola modifica ha rotto un caso d'uso.

Approccio

Architettura su tre layer — sorgenti immutabili (manuale OCR'd doppio), wiki autoritativo a quattro categorie editabile, agente conversazionale con 17 tool — costruita in AI-assisted development con Claude Code. Sei scelte distinguono il progetto da un RAG standard.

rendering diagram…

Doppio OCR open-source come barriera all'errore di lettura silenzioso

Niente single-pass: il manuale passa contemporaneamente attraverso MinerU (struttura, tabelle, layout) e pdfplumber (raw text — recupera formule grafiche rese come glifi vettoriali che il primo perde). I due output vivono affiancati per famiglia con fonte distinto nel DB; il modello vede entrambi via ricerca semantica. Caso reale: l'altezza spondina K = 115,6 mm in TANDEMBOX antaro era invisibile a MinerU perché disegnata graficamente, pdfplumber l'ha catturata come testo flat. È il pattern di affidabilità che mi porto da progetto a progetto: due estrattori complementari sono sempre più solidi di uno solo, anche del migliore.

Wiki autoritativo a quattro categorie con editor live per il tecnico interno

Sopra ai PDF estratti vive un wiki markdown editabile via UI dedicata: Distinte (composizione canonica per famiglia: pattern dei codici, varianti, optional), Guide (decision tree per scelte: "LEGRABOX o MERIVOBOX?"), Regole (vincoli tecnici trasversali con frontmatter YAML — peso anta, numero cerniere, dimensionamento), Famiglie (schede narrative "cos'è X, quando sceglierla / NON sceglierla"). Il tecnico interno del cliente modifica un MD da sidebar+textarea+preview live; il salvataggio crea backup .bak.{ts} automatico (retention 10), un validator AI controlla pre-save che non ci siano codici inventati o sigle interne. Un reindex selettivo riallinea il DB in 2 minuti. Il cliente è proprietario effettivo della propria knowledge base.

Single source of truth con derivazione MD→Python→system prompt

Per le 4 famiglie di sistemi cassetto (LEGRABOX, MERIVOBOX, TANDEMBOX antaro, METABOX) i parametri tecnici (altezze spondina, NL ammesse, portate, pattern del codice articolo, colori validi) inizialmente vivevano in 3 posti: i markdown autoritativi, le tabelle inline del system prompt, un dict Python consumato dal tool composito. Ogni cambio = 3 file da toccare = divergenza silenziosa garantita. L'ho ricondotto a una sola fonte editabile: il modulo distinta_parser.py legge i D*.md e produce sia il dict Python (lazy-cached, drop-in del legacy) sia il markdown della sezione del prompt (sostituito al boot via placeholder). Cambiare un valore nel MD si riflette automaticamente al riavvio in entrambi i livelli derivati. È la disciplina che blocca il debt più costoso: il valore che diverge in silenzio.

Gate logic server-side con marker espliciti + tool_choice forzato API-side

Il pattern di context engineering più distintivo del progetto. Un classifier Python deterministico (regex su keyword + scansione di tutto lo storico user, non solo l'ultimo turno) riconosce due stati nei flussi di configurazione cassetto:

  • dati chiave mancanti (manca NL o manca portata) → inietta nel messaggio una direttiva interna [blumcat-gate:portata] che impedisce al modello di chiamare il tool, gli vieta di inventare default e gli impone una sola domanda con [OPZIONI: 40 kg | 70 kg];
  • dati chiave completi → inietta [blumcat-gate:done] + la distinta-template della famiglia + forza il tool composito tramite il parametro tool_choice nativo dell'API ("DEVI chiamare assemble_distinta_cassetto al primo round, no se no ma").

Il principio è che le garanzie hard non passano dal prompt ("DEVI fare X" è un suggerimento, non un contratto) ma da codice deterministico + parametri API. Il vantaggio: ogni passaggio è ispezionabile, testabile, debuggabile; e funziona con Haiku come con Sonnet.

Routing modello calibrato sull'eval set

Non "Haiku per tutto, Sonnet quando serve" ma routing per rischio di allucinazione: lookup secchi (codice articolo, sigla del glossario) vanno su Haiku con cache ben sfruttata; configurazioni, decisioni multi-step, "differenza tra X e Y", calcoli combinati vanno su Sonnet. Le regex del router sono state calibrate dall'eval set: passando da 5 a 15 casi sono emersi 4 bug reali in produzione (regex portata che non riconosceva 25/30/65 kg, parsing famiglia che mancava "aventos hk" senza suffisso) — fixati con tre one-liner perché individuati prima che diventassero un report utente. Il routing è codice come ogni altro: si testa.

Eval set deterministico come gate di refactor e di deploy

Una trentina di casi end-to-end (API reale, niente mock) eseguiti via il modulo evals/ in ~5 minuti per ~€0,30 per run. Ogni caso definisce una conversazione e un set di assertions: tool che il modello DEVE chiamare, tool che NON DEVE chiamare, marker che il classifier DEVE iniettare, codici che la risposta DEVE contenere (verificati in DB), e — fondamentale — blacklist di codici storicamente allucinati (770M500L, 770M500R, 378D5002SA...) che la risposta NON DEVE mai contenere. Ogni bug fissato diventa un nuovo caso (regression test organico). È quello che ha permesso di fare il refactor "single source of truth" descritto sopra senza paura: baseline 28/30 PASS prima del cambio, 28/30 PASS dopo → il refactor non ha rotto nulla. Senza eval set, sarebbe stato un atto di fede.

Risultato

In produzione interna LAN dal 2026 — bot con 17 tool esposti, ricerca semantica su 4.061 chunk con embedding 768d, copertura su 45 famiglie e 1.936 codici, foto e PDF locali per 235 prodotti, citazioni inline con preview cliccabile delle 758 pagine del manuale pre-renderizzate. Il routing tiene il costo medio nell'ordine dei 5–15 centesimi per conversazione grazie al prompt caching aggressivo (sconto del 90% sui ~7–8k token "fissi" di catalogo + tools + distinta-template), un ordine di grandezza al di sotto di una conversazione full-Sonnet senza cache.

Il messaggio tecnico più trasferibile, per chi guarda da fuori, è che in un dominio dove l'allucinazione non è un fastidio ma un costo concreto (un ordine sbagliato a fornitore) il prompt engineering da solo non basta. Servono pattern di context engineering: deterministica server-side dove possibile (classifier, gate, push del contesto), parametri API hard dove necessario (tool_choice), single source of truth per ogni valore che rischia di duplicarsi, eval set come gate di tutto. È il pattern che ho consolidato qui e che applico sui nuovi progetti di knowledge base AI per PMI manifatturiere — un livello sopra al framework descritto in architettura-wiki, con focus sulle garanzie hard che il dominio tecnico richiede.