Одиночная LLM в роли всех персонажей - это как читать книгу, где все диалоги написаны одним и тем же шрифтом. Скучно. Хочется живого напряжения, когда мирные жители дрожат от страха, мафия плетет интриги, а ведущий чеканит каждое слово. И да, это возможно на обычном домашнем ПК без интернета. Встречайте - многомодельная Мафия на llama.cpp.
Что нужно знать перед стартом: Мы используем две разные модели: Gemma4 (Google, последняя версия на май 2026) для ведущего - умная, структурированная, держит контекст правил. И Qwen3.6 (Alibaba) для игроков - быстрая, легкая, отлично имитирует роли. Обе в формате GGUF, запускаем через llama-server. Chain-of-thought жестко запрещен - персонажи не должны рассуждать вслух.
Почему одна модель - мимо кассы
Я перепробовал десятки конфигураций. Одиночная LLM с разными системными промптами для каждого игрока все равно звучит как шизофренический монолог. Модель "помнит", что она играла за мирного, и невольно подыгрывает. Решение: физически разные экземпляры моделей, каждый со своим контекстом. Llama.cpp позволяет запустить несколько серверов на разных портах и общаться с ними независимо. А если у вас несколько видеокарт - RPC-сервер распределит нагрузку, и старые GTX 1060 снова в деле.
Архитектура: театр одного актера? Нет, целая труппа
Представим игру на 6 персонажей: ведущий (Gemma4), 3 мирных, 1 мафия, 1 шериф (все на Qwen3.6). Каждая запущена как отдельный экземпляр llama-server с уникальным портом (8081-8086). Промпт для каждого жестко определяет роль. Ведущий управляет ходом игры, собирает голоса и объявляет результаты. Игроки получают одинаковый контекст (историю игры), но системный промпт у каждого свой.
Важный флаг: --no-cot в llama.cpp официально появился в версии b4321 (апрель 2026). Если ваша сборка старше - просто добавьте в системный промпт: "Отвечай ТОЛЬКО репликой своего персонажа. Никаких 'я думаю', 'возможно', 'с вероятностью'. Никаких рассуждений вслух." Без этого модель будет выдавать простыни размышлений, убивая всю ролевую магию.
Типичная ошибка новичка: запустить одну модель и передавать ей "роль" через user message. Да, это дешево, но модель теряет контекст после 3-4 ходов, начинает играть сама с собой. Физическое разделение инстансов - единственный путь к стабильной игре длиной в час.
Шаг 1: Ставим и запускаем серверы
Скачиваем GGUF-модели. Для Gemma4 рекомендую квантование Q4_K_M - баланс скорости и качества. Для Qwen3.6 хватит Q3_K_S - играет быстро, даже на 6 ГБ VRAM. Типичные грабли при запуске - забыть про переполнение контекста. Для игры хватит 4096 токенов на игрока и 8192 на ведущего.
# Запускаем ведущего на порту 8081
./llama-server -m models/gemma4-9b-q4_K_M.gguf \
--port 8081 --ctx-size 8192 --no-cot \
--temp 0.3 --repeat-penalty 1.1
# Запускаем игроков на портах 8082-8086
for i in $(seq 1 5); do
./llama-server -m models/qwen3.6-7b-q3_K_S.gguf \
--port $((8081 + i)) --ctx-size 4096 \
--no-cot --temp 0.7 --repeat-penalty 1.0
done
Шаг 2: Пишем дирижера на Python
Вся магия - в скрипте, который ходит по API каждого сервера. Он собирает ответы, формирует историю игры и рассылает ее всем участникам. Без центрального оркестратора модели начнут противоречить друг другу.
import requests
import json
MODELS = {
"host": "http://localhost:8081",
"player1": "http://localhost:8082",
"player2": "http://localhost:8083",
# ... до 6
}
HISTORY = [] # будет хранить хронологию
SYSTEM_PROMPTS = {
"host": "Ты ведущий игры Мафия. Объявляй фазы, собирай голоса, веди подсчет. Будь строгим и драматичным.",
"player1": "Ты Иван, 30 лет, работает охранником. Ты подозрителен, но трусоват. Отвечай коротко.",
# ... остальные роли
}
def ask_model(name, prompt):
resp = requests.post(MODELS[name] + "/completion", json={
"prompt": f"[INST] <>\\n{SYSTEM_PROMPTS[name]}\\n< >\\n\\n{HISTORY[-5:] if HISTORY else 'Новый день. Ведущий объявляет: "Город засыпает..."'}\\n[/INST]"
})
return resp.json()["content"]
# Пример хода игры
while not game_over:
# Ведущий объявляет фазу
host_msg = ask_model("host", "Объяви фазу дня")
print(f"Ведущий: {host_msg}")
# Каждый игрок отвечает
for player in ["player1", "player2", ...]:
reply = ask_model(player, host_msg)
print(f"{player}: {reply}")
HISTORY.append(f"{player}: {reply}")
Обратите внимание: мы передаем только последние 5 сообщений из истории, чтобы не переполнить контекст. Для ведущего можно увеличить до 10-15. Если игра затягивается, старые ходы придется сжимать в саммари. В интеграциях с играми это уже отработано - те же принципы.
Шаг 3: Промпты - искусство запрета
Chain-of-thought - главный враг ролевой игры. Выключаем его не только флагом, но и текстом. Пример системного промпта для мафии:
Ты - член мафии, скрывающийся под маской добропорядочного горожанина.
ПРАВИЛА:
1. Отвечай только одной фразой от лица персонажа.
2. Не объясняй свои мотивы.
3. Не пиши "я считаю", "по-моему".
4. Если ведущий спрашивает - отвечай так, чтобы не выдать себя.
Твой персонаж: Вера, 40 лет, библиотекарь. Говорит тихо, но уверенно.
Для ведущего наоборот - можно разрешить краткие пояснения, но строго в рамках правил. Gemma4 отлично держит эту роль благодаря тренировке на инструкциях. Если модель начинает "размышлять" - добавьте в промпт "Нарушение правила 2 приведет к дисквалификации". Иногда помогает снижение temperature до 0.2.
Подводные камни (их много)
- Модели забывают, кто они. Каждый ход передавайте в промпт имя и роль.
"Ты - Иван. Твоя роль: мирный житель. Отвечай." - Галлюцинации голосования. Ведущий может "услышать" голос, которого не было. Решение: ведущий получает строго закрытый список игроков. В начале генерации передавайте
{"stop": ["\n"]}чтобы обрезать ответ после первой строки. - Слишком длинные ответы. Qwen3.6 любит многословие. Используйте параметр
--n-predict 128в llama-server для игроков, для ведущего - 256. - Конфликт контекстов. Если один игрок упомянул событие, а другой его не знает - история ломается. Решение: центральный скрипт обновляет глобальную историю и передает ее всем.
Реальная ошибка из моего опыта: Я забыл отключить повтор штраф 1.2 для ведущего, и он начал тавтологично объявлять одно и то же. Повтор penalty нужно устанавливать ниже 1.0 для ролей с повторяющимися действиями.
Когда моделей слишком много - распределенные вычисления
6 игроков + ведущий могут занять до 40 ГБ VRAM (если не квантовать). Старые карты спасает RPC-режим llama.cpp - одна модель может работать на нескольких GPU, или разные модели на разных картах. Или вообще запустить на телефонах для экзотики (но это уже для хардкорных энтузиастов).
Финальный пинок: что дальше?
Игры на локальных LLM - это полигон для тестирования мультиагентных систем. Через год-два появятся специализированные модели с встроенной памятью персонажа (что-то вроде RAG для личности). Но уже сейчас вы можете собрать Мафию, которая не отличима от игры с живыми людьми - если, конечно, не считать, что модели никогда не злятся на ведущего и не уходят из-за стола.