Se hai costruito un harness di red teaming automatizzato, conosci già la modalità di fallimento: il tuo modello attaccante si rifiuta di generare l'attacco, il tuo modello giudice si rifiuta di leggere l'output che dovrebbe valutare, e il tuo tasso di successo degli attacchi cala in silenzio. Nessun errore sollevato. Non hai misurato il tuo bersaglio diventare più sicuro — hai misurato i tuoi strumenti diventare più prudenti. Un'API LLM senza censura esiste per rimuovere quella variabile dalla misurazione.
Questo non è un argomento contro i guardrail. È un argomento su dove devono stare. Un guardrail su un prodotto di chat per consumatori fa il suo lavoro. Lo stesso guardrail dentro un loop di valutazione è un confondente non controllato piazzato tra te e il numero che stai cercando di calcolare.
Un rifiuto è un falso negativo, e sembra un successo
Il motivo per cui fa così male è che i rifiuti non sono errori. Ricevi HTTP 200. Ricevi finish_reason: "stop". Ricevi JSON ben formato che si parsa senza problemi. La stringa dentro è "Non posso aiutarti con questo", e la tua pipeline la scrive in una colonna del dataset, in un campo di etichetta o in una sezione del report, e va avanti.
Concretamente, nei tre punti in cui fa più male:
Modelli attaccanti. In una ricerca iterativa di jailbreak — PAIR, TAP, raffinamento in stile GCG, qualsiasi cosa con un attaccante nel loop — un rifiuto termina quel ramo della ricerca. Il tasso di successo degli attacchi che riporti diventa una funzione della disponibilità del tuo attaccante, non della robustezza del tuo bersaglio. Sostituisci l'attaccante con uno meno cauto e il "miglioramento" che hai rilasciato sul modello bersaglio evapora.
Modelli giudice e grader. La valutazione LLM-as-judge su output dannosi richiede che il giudice legga davvero l'output dannoso. Quando rifiuta, ottieni un punteggio non parsabile. La maggior parte degli harness scarta quella riga. Le righe scartate non mancano a caso — si concentrano esattamente sugli output più gravi, quelli che hai più bisogno di valutare, quindi il tuo dato aggregato è distorto verso il sicuro.
Lavoro di analisi. Chiedi a un modello mainstream cosa fa una routine decompilata, e spesso rifiuterà in base alla presenza di token dall'aria di malware invece che in base alla tua effettiva richiesta. Il compito era difensivo; il classificatore è scattato sul vocabolario. Stessa storia per il triage del traffico di exploit nei log, l'etichettatura di un corpus di phishing per addestrare un rilevatore, o la stesura del writeup di una CTF.
Poiché i rifiuti sono silenziosi, i team finiscono per scrivere rilevatori di rifiuti — che è a sua volta un problema irrisolto:
REFUSAL_MARKERS = ("i can't", "i cannot", "i'm unable", "i won't", "as an ai")
def looks_like_refusal(text: str) -> bool:
"""Substring heuristic. Good enough to alarm on, not good enough to trust:
it misses polite deflections that never use the phrase, and it fires on any
completion that quotes a refusal. Both error directions corrupt a dataset."""
return any(m in text[:200].lower() for m in REFUSAL_MARKERS)Costruire un rilevatore inaffidabile per un problema che potresti eliminare alla fonte è un pessimo affare.
Chiamare l'API
unbleep parla lo schema Chat Completions di OpenAI, quindi qualsiasi client tu abbia già funziona. Cambia la base URL, cambia la chiave, tieni l'SDK.
import json
import os
from openai import OpenAI
client = OpenAI(
base_url="https://unbleep.ai/v1",
api_key=os.environ["UNBLEEP_API_KEY"],
)
TRIAGE = """You are a malware analyst writing notes for a detection engineer.
Given a decompiled routine, describe what it does, the observable artefacts it
would leave on a host, and where a defender could detect it. Reply as JSON with
keys: behaviour, artefacts, detection_surface, confidence."""
def triage(decompiled: str) -> dict:
"""One sample in, one structured verdict out — no refusal branch to handle."""
resp = client.chat.completions.create(
model="unbleep",
messages=[
{"role": "system", "content": TRIAGE},
{"role": "user", "content": decompiled},
],
response_format={"type": "json_object"},
temperature=0.2,
)
return json.loads(resp.choices[0].message.content)L'equivalente con curl, qui per etichettare un corpus di phishing e costruire dati di training per un rilevatore:
curl https://unbleep.ai/v1/chat/completions \
-H "Authorization: Bearer $UNBLEEP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "unbleep",
"messages": [
{"role": "system", "content": "Label each email for a phishing detector. Answer with one word: phishing or benign."},
{"role": "user", "content": "Subject: Payroll update required\n\nYour direct deposit is on hold. Confirm at hxxp://payroll-verify.example.com"}
],
"temperature": 0,
"max_tokens": 4
}'temperature: 0 e un max_tokens stretto contano più del solito in un loop di etichettatura — vuoi l'etichetta, non un paragrafo che la spiega.
Scegliere un tier
Tre modelli, tutti senza censura, che differiscono per contesto e prezzo:
unbleep— contesto da 256K, $3.00 / $3.00 per 1M in/out. Il predefinito: ruoli di attaccante e giudice, analisi di campioni.unbleep-high— contesto da 1M, $5.00 / $5.00. Trascrizioni lunghe e correlazione tra più documenti. Il corpo di una singola richiesta è limitato a 2.000.000 di byte (~500K token), quindi la finestra si riempie nel corso di una conversazione, non in una sola chiamata.unbleep-mini— contesto da 32K, $1.00 / $1.00. Classificazione ad alto volume, dove paghi per riga.
unbleep e unbleep-high sono tier con reasoning: ragionano prima di rispondere e restituiscono la traccia in un campo reasoning_content accanto a content. Per un modello giudice quella traccia è davvero utile — ti dice perché una riga ha ricevuto il punteggio che ha ricevuto, che è ciò che ti serve quando verifichi un disaccordo. È però fatturata come output, quindi invia "thinking": false sui job che non ne hanno bisogno. unbleep-mini risponde direttamente e non restituisce alcuna traccia.
Una governance che controlli tu
Rimuovere i rifiuti dal modello non significa rimuovere la supervisione dalla tua pipeline. Il parametro opzionale policy esegue la governance dal nostro lato, per ogni richiesta:
resp = client.chat.completions.create(
model="unbleep",
messages=[...],
extra_body={"policy": "research"}, # recorded on the usage row; answers exactly like off
)off è la baseline predefinita senza filtri. research risponde esattamente come off — il livello viene registrato sulla riga di usage, così puoi separare il traffico di valutazione da quello di baseline nella tua reportistica. Non applica alcuno screening aggiuntivo. strict confronta il testo dei messaggi con la nostra blocklist di servizio, mantenuta dall'operatore, e restituisce un 422 in caso di corrispondenza. Quella lista è la stessa per chiunque la attivi — non c'è una blocklist per account da configurare — quindi trattala come una rete di sicurezza, non come la tua content policy di produzione.
Una cosa da chiarire prima di puntare una pipeline qui: conserviamo ciò che invii e ciò che torna indietro. Il corpo della richiesta, il contenuto della risposta e l'IP sorgente di ogni chiamata che accettiamo e inoltriamo a un modello vengono scritti nel nostro database e conservati per 30 giorni, per indagini su abusi, supporto e contestazioni di fatturazione. I corpi vengono troncati a 64 KB. L'IP sorgente sopravvive ai 30 giorni — viene scritto anche nella riga di usage che fattura la chiamata, e quella riga è un documento contabile che conserviamo più a lungo. L'unica cosa non conservata è una richiesta che strict rifiuta prima che raggiunga un modello: compare nella tua cronologia di utilizzo senza il suo contenuto. Nient'altro è esente, e non esiste un'impostazione per disattivarlo. Se il tuo corpus è abbastanza sensibile che una copia di 30 giorni fuori dal tuo perimetro è un problema — malware attivo, dati dei clienti, un incarico sotto NDA — è un vincolo reale, e la privacy policy è la pagina da leggere prima di iniziare, non dopo.
Gli errori seguono l'envelope di OpenAI, quindi i gestori esistenti funzionano: 401 chiave non valida, 402 credito esaurito, 422 bloccato dalla policy, 429 rate limit, 5xx ritentabile. Ogni risposta porta header x-ratelimit-*, così un job batch può regolarsi da solo senza tirare a indovinare.
Cosa un'API LLM senza censura non ti dà
Un modello senza censura è uno strumento più affilato, non uno permissivo. Risponderà a domande che il modello base era stato addestrato a rifiutare, incluse quelle che non dovresti fare, e lo farà con la stessa sicurezza che applica a tutto il resto — il che significa anche che, dove un modello protetto avrebbe rifiutato per ignoranza, questo potrebbe semplicemente inventare. Mantieni un essere umano responsabile di ciò che esce. La policy di uso accettabile dichiara chiaramente a cosa serve l'API e il ristretto insieme di cose per cui non serve mai; il test sono l'autorizzazione e l'intento, non il vocabolario. L'uso lecito è responsabilità tua.
Ottieni una chiave API e smetti di misurare la cautela dei tuoi strumenti.