Ваша локальная LLM говорит то, что не нужно? Знакомо
Ты запускаешь свою любимую модель через llama.cpp, всё летает, но вдруг она начинает упоминать то, что тебе не нужно. Рекламные слоганы, имена конкурентов, специфический сленг или просто мусорные фразы, которые модель подхватила из трейна.
Стандартные методы — --grammar, ручная постобработка, регулярки на стороне потребителя. Но если хочешь резать по живому — на уровне токенов, до того как строка попала в stdout, — нужно другое.
Я наткнулся на репозиторий llama-filter (вымышленное название для примера, но суть та же — если такого нет, ты можешь собрать сам за 20 минут). Идея простая: обёртка вокруг llama.cpp, которая перехватывает вывод чанками и бьёт по шаблонам, прежде чем модель успевает договорить до конца.
Сразу предупрежу: это не серебряная пуля. Если модель решила, что следующее слово — «наркотики», то блокировка сработает, но в контексте может появиться прореха. Однако для 90% бытовых блокировок (мат, спам-триггеры, упоминания запрещённых тем) хватает за глаза.
Как это работает (быстро, без лишней магии)
В llama.cpp есть режим интерактивного вывода — токены приходят пачками. Скрипт читает stdout, накапливает буфер, и как только встречает запрещённую фразу — заменяет её на цензуру (например, [BLOCKED]) либо обрывает генерацию на этом месте и вставляет принудительный перенос строки.
Ключевая фишка — работа на уровне подстроки. Не нужно ждать окончания предложения. Это значит, что фразу «как заработать миллион» можно заблокировать посреди генерации, и нейросеть не успеет выдать остаток.
1 Забираем репозиторий (или пишем сами)
В README проекта лежит единственный Python-скрипт filter.py и файл-список банов blocklist.txt. Клонируем:
git clone https://github.com/example/llama-filter.git
cd llama-filterЕсли репозиторий недоступен — вот минимальная версия, которую можно запустить сразу:
import subprocess, sys, re
# Запрещённые фразы (регистронезависимые)
BANNED = [
r'как заработать деньги',
r'переходи по ссылке',
r'офигенный курс',
]
BLOCKED_REPLACEMENT = '[ЦЕНЗУРА]'
def process_output(line: str) -> str:
for pattern in BANNED:
line = re.sub(pattern, BLOCKED_REPLACEMENT, line, flags=re.IGNORECASE)
return line
if __name__ == '__main__':
# Запускаем llama.cpp с нужной моделью
proc = subprocess.Popen(
['/usr/local/bin/llama-cli', '-m', 'model.gguf', '--interactive'],
stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1
)
for raw_line in proc.stdout:
sys.stdout.write(process_output(raw_line))
sys.stdout.flush()Да, это примитивно. Но в основе — именно такой пайп.
Чем это лучше встроенных банов llama.cpp?
llama.cpp умеет --grammar — это мощь, но она работает на уровне формата, а не содержания. Забанить слово «спам» через грамматику — извращение. Постобработка через sed или grep — тормозит и режет по готовому тексту, а не прерывает генерацию.
Сравним подходы в таблице:
| Метод | Скорость | Прерывание на лету | Гибкость паттернов |
|---|---|---|---|
--grammar | Высокая | Нет | Только структура |
| sed/grep пост-фактум | Средняя | Нет | Высокая |
| Наш скрипт | Высокая | Да | Высокая |
Как видишь, комбинация «прерывание + гибкость» даёт только скриптовая обёртка. Минус — надо держать процесс-потомок, но для локального использования это не проблема.
Когда это реально выручает
- Чат-бот для техподдержки: запрещаем упоминание конкурентов, мат, рекламные ссылки.
- Персональный ассистент: блокируем фразы вроде «я не могу помочь» или «обратитесь к специалисту» — чтобы бот не сливался.
- Образовательные проекты: убираем неуместный контент (насилие, наркотики) из генерации для детей.
- Тестирование безопасности: проверяем, обходит ли модель блокировку — как в случае с Unicode-атаками.
Особенно актуально, если модель склонна к паразитным паттернам — она может сама генерировать нежелательные цепочки, и блокировка на лету их обрывает.
Типичные грабли (как их обойти)
Первое — не пиши в списке банов слишком короткие слова. «Куплю» заблокирует любое упоминание, включая «скуплю» или «выкуплю». Лучше используй границы слов: \bкуплю\b.
Второе — регистр. Наш скрипт выше использует re.IGNORECASE, и это правильно, но если модель иногда пишет странные Unicode-символы (например, кириллица в английском), регулярка может пропустить. Тут поможет нормализация через unicodedata.normalize().
Третье — производительность. Если список из 500 паттернов, а скорость генерации 50 токенов/сек, на каждую строчку уходит пара микросекунд — незаметно. Но если паттерны содержат сложные lookahead, может подтормаживать. Держи регекспы простыми.
Важно: Если ты используешь llama.cpp версии младше, чем 2.4, — обнови, иначе скрипт может не перехватить интерактивный вывод из-за бага с буферизацией. Читай про кризис стабильности llama.cpp.
Кому этот инструмент — мастхэв
- Разработчикам продуктов на локальных LLM (чат-боты, RAG).
- Администраторам, которые хотят обезопасить корпоративный LLM-сервер.
- Энтузиастам, запускающим сборку llama.cpp под своё железо — легко интегрируется в пайп.
- Всем, кто задолбался объяснять нейросети, что «не пиши про секс».
Скрипт не требует GPU, не грузит систему — просто тонкий прослойка между stdout и твоими глазами. Идеально для тех, кто хочет контролировать каждое слово, но не готов писать кастомный семплер на C++.
Не жди чуда: бан-лист не уберёт модель от генерации нежелательного контекста — она просто заменит слово на заглушку. Но если тебе нужно гарантированно вырезать «реферальную ссылку» из ответа ассистента — это решение.
Совет напоследок: добавь в блок-лист не только фразы, но и триггеры начала спама — например, если модель пишет «Кстати, вот», а дальше идёт реклама — блокируй «Кстати, вот» целиком. Так рвёшь паттерн до того, как он развернётся.
И не забывай обновлять список банов — модель может подхватить новые обороты. Регулярно просматривай логи и дополняй blocklist.txt. Всё остальное — дело техники.