Un bot antispam Telegram su un decision model locale
Affidate il vostro gruppo Telegram a un bot di moderazione nel cloud e rileggete le clausole dell’accordo. Ogni messaggio che i membri scrivono, in ogni lingua, compresi quelli sulla loro salute, sulla loro politica e sui loro litigi delle tre di notte, viaggia verso il server di un venditore, viene classificato da un modello che non potete ispezionare, e torna indietro sotto forma di verdetto. Il contratto dice che il computer di qualcun altro legge le conversazioni della vostra comunità prima della comunità stessa. Io ho tenuto la moderazione e buttato via il resto: un bot di moderazione Telegram che fa girare un decision model Laya interamente sulla mia macchina, dove nessun messaggio esce mai, e dove l’intera pipeline è un centinaio di righe di Python che si leggono in un pomeriggio.
Una pipeline di moderazione senza cloud
L’architettura è volutamente noiosa. I messaggi entrano in una pipeline. La pipeline esegue i moduli in ordine di priorità. Ogni modulo può restituire KEEP, DELETE o FLAG, e il primo DELETE vince e cortocircuita tutto il resto. Admin, il bot stesso e i post da admin anonimo passano senza controlli. Questo è l’intero automa a stati, e siccome ogni modulo è una classe registrata con un decoratore, aggiungere una quarta regola al gruppo significa aggiungere un file, non toccare il cuore.
Il problema di ingegneria interessante è che tre regole diverse vogliono consultare lo stesso modello sullo stesso messaggio. L’anti-spam vuole sapere che tipo di messaggio è. Il filtro saluti vuole sapere se è soltanto un complimento. Far girare il modello due volte per messaggio raddoppierebbe latenza ed energia senza alcuna conoscenza in più, quindi il verdetto viene calcolato pigramente, una volta sola, e condiviso:
async def classify(self) -> LayaVerdict:
"""Restituisce il verdetto Laya condiviso, calcolandolo alla prima chiamata."""
if self._verdict is None:
async with self._verdict_lock:
if self._verdict is None: # riverifica: un altro chiamante potrebbe aver vinto
self._verdict = await self.classifier.classify(self.text)
return self._verdict
È un pattern double-checked locking degli anni Novanta a proteggere un modello decisionale del 2026, e significa che il gruppo paga esattamente un forward pass per messaggio, per quanti moduli chiedano. Il classificatore stesso carica i pesi una volta all’avvio, fa un warm-up con una predizione gettata così che un’eventuale deriva di schema fallisca al boot e non sul primo messaggio vero, e serializza le chiamate con un lock, perché un forward pass alla volta è il modo in cui questa classe di modelli vuole essere servita.[1]
Tre guardie, un modello
Il filtro spam lavora in due stadi. Un manipolo minuscolo di regex quasi certe elimina l’ovvio prima ancora che il modello sia consultato, perché spendere 130 millisecondi di inferenza su un messaggio che è letteralmente un link a un giveaway di criptovalute è uno spreco di un buon modello. Tutto il resto va al modello decisionale, la cui risposta è una probabilità, e la probabilità incontra una scala a pioli:
verdict = await ctx.classify()
if verdict.spam_probability >= self.config.spam_delete_threshold: # 0.85
return ModuleResult.delete("laya: spam", confidence=verdict.spam_probability)
if verdict.spam_probability >= self.config.spam_flag_threshold: # 0.60
return ModuleResult.flag("laya: spam borderline", confidence=verdict.spam_probability)
return ModuleResult.keep()
Lo spazio tra le soglie è dove vive il design. Cancellare un messaggio legittimo è l’errore costoso della moderazione, molto più costoso di uno spam lasciato su per un minuto, quindi la banda tra 0,60 e 0,85 non cancella nulla: registra un FLAG con la confidenza attaccata, e quei log sono i dati di calibrazione per stringere i pioli in futuro. Il modello propone; la soglia dispone.
Il filtro saluti è quello che mostra perché un modello decisionale si guadagna il posto. Un messaggio di solo saluto, «ciao», «gm», «buongiorno», «hola», è rumore in un gruppo affollato, ma intercettarlo con le regex significa mantenere una lista di saluti in ogni lingua parlata dai membri, e i saluti sono il rumore più polimorfo esistente. Il modulo applica invece guardie che non richiedono alcuna intelligenza, media, link e tutto ciò che supera i 200 caratteri viene tenuto senza chiamare il modello, e consegna tutto il resto al checkpoint multilingue con una sola domanda: questo messaggio è soltanto un saluto? Un modello, ogni lingua, nessuna lista di parole.[1]
Il terzo modulo è il meno glorioso e il più usato: cancella gli avvisi di entrata e uscita e i messaggi di servizio degli altri bot, dieci secondi dopo la pubblicazione così che gli umani possano vederli, perché anche la cosa pubblica di un gruppo comprende il non dover guardare i macchinari fare la toeletta a se stessi.
Perché un modello decisionale e non un LLM
La risposta onesta è che per questo lavoro ogni asse punta nella stessa direzione. Privacy: i messaggi sono il discorso privato di una comunità, e un modello decisionale in locale significa che i dati non vanno da nessuna parte; nel repository non c’è nessuna API key perché non c’è nulla a cui autenticarsi. Costo: un LLM in hosting che legge il vostro gruppo tutto il giorno fattura a token, per sempre; questo costa l’elettricità per tenere residente un modello piccolo, circa 700 megabyte di pesi e qualche watt. Latenza: un forward pass su CPU costa circa 130 millisecondi; un viaggio di andata e ritorno verso un modello in hosting costa altrettanto in puro sovraccarico, prima del primo token. Determinatezza: il modello restituisce probabilità su uno schema fisso, quindi il codice che agisce è un confronto tra soglie, non un parser che può essere sorpreso. E multilinguismo: un solo checkpoint copre l’intero gruppo, che è esattamente ciò che le liste di regex non potranno mai fare.[2]
C’è anche una nota politica. Un moderatore che fa girare pesi aperti sul vostro hardware non può essere spento dal regime export di nessuno, non può essere messo in dark period dalla svolta di un venditore, e non può far trapelare la vostra comunità in un data breach dell’API di qualcun altro. Ho sostenuto in generale che i pesi aperti sono irreversibili; qui è alla scala di una chat di gruppo.[3]
Le ragioni dell’altra parte
L’assetto nel cloud esiste per delle ragioni, e questo design paga costi veri per rifiutarle. Un modello di frontiera legge il contesto meglio di un encoder da 322 milioni di parametri: il sarcasmo, le battute interne al gruppo, il saluto che porta un coltello dentro. Da sole, regex ed euristische basterebbero per molti gruppi, ed è per questo che esiste la corsia veloce; il modello serve il residuo che le regex non sanno nominare. Un modello decisionale è inoltre una cosa in più da calibrare, e la calibrazione non è facoltativa: il checkpoint multilingue viene distribuito deliberatamente non calibrato, incline a riferire 100 per cento e 0 per cento, e le sue soglie sono congetture finché non le avete misurate sui messaggi veri del vostro gruppo.
Così il bot nasce come dovrebbe nascere ogni moderatore onesto: parte in modalità dry-run, dove registra ogni decisione che prenderebbe e non cancella niente, e include uno script di valutazione che prende un file JSONL dei messaggi veri del vostro gruppo e riferisce cosa avrebbero fatto le soglie. Fatelo girare zitto per una settimana. Guardate i flag. Spostate i numeri. Solo allora lasciatelo toccare qualcosa.[4]
Moderare non è capire; è decidere, a volume, in pubblico, con la pazienza di qualcuno dall’altra parte di ogni errore. Per quel lavoro, il modello che non chiama mai casa non è un compromesso. Il posto più economico per un segreto è una macchina che non parla con nessuno.
- erubboli/telegram_spam_bot, il bot descritto in questo articolo: pipeline con moduli in ordine di priorità e cortocircuito al primo DELETE, un solo verdetto Laya condiviso per messaggio via double-checked locking, corsia veloce regex, banda FLAG tra le soglie, dry-run predefinito, guardie media/link/lunghezza prima del modello, 36 test che passano offline con un classificatore finto. Modello:
convaiinnovations/laya, sottocartellamultilingual. - NandhaKishorM/laya, Apache-2.0: il modello decisionale, i suoi tre tipi di domanda e il suo checkpoint multilingue. La categoria e l’alternativa chiusa che sottrae pubblico sono trattate in Dagli LLM ai System One models: Jev e Laya.
- L’AI Act regola le fatture. I pesi aperti non ne emettono., sull’enforcement che vive dove ci sono le fatture, e sul perché i pesi rilasciati non si possono ritirare.
- La scelta delle soglie e lo script di valutazione sono trattati in dettaglio in Calibrare Laya: formulazione, direzione e soglie delle domande, il prossimo articolo della serie.