Документация Инструменты

Реплики и локальные модели

Любой юнит в игре может заговорить от любого имени — в облачке над головой. Подключите локальную LLM: фраза на входе, ответ появляется над юнитом.

Облачки — зрелищный слой: на исход игры они не влияют и подходят для стримов, комментирования и отладки.

  • говорить может любой юнит от любого имени; несколько юнитов могут говорить одновременно;
  • размер шрифта, цвет, ширина, хвостик, прозрачность и скорость печати настраиваются для каждого облачка отдельно;
  • можно напрямую подключить локальную LLM (LM Studio) с потоковым выводом: облачко обновляется по мере генерации.

Из бота

Проще всего — встроенный в SDK метод say:

g.say(hero, "За мной, в атаку!", seconds=4)

Запуск и интерфейс

Проще всего — главная страница Farsight «Центр управления»: сначала нажмите «Локальная LLM → Запустить и загрузить модель» (локальный сервер LM Studio + загрузка настроенной модели в видеопамять), затем «Облачка реплик → Запустить». На карточках можно смотреть логи, останавливать и перезапускать.

Интерфейс — на странице «Облачка реплик» в левой панели Farsight: заставить юнита говорить (выбрать юнита, написать текст, настроить стиль, поговорить с моделью), посиделки крестьян, диалоги в кадре, реакция на события, настройки модели; всё применяется к экземпляру, выбранному в верхней панели.

Можно и из командной строки:

python speech/speak_launch.py              # запустить сервис локальной модели + загрузить и прогреть модель + поднять API облачков
python speech/speak_launch.py --restart    # перезапустить API после изменения кода
python speech/speak_launch.py --stop       # остановить API и выгрузить модель из видеопамяти

Каждый шаг работает по принципу «уже запущено — пропустить», поэтому повторный запуск ничего не ломает.

HTTP API

По умолчанию http://127.0.0.1:8872/ (порт задаётся в ports.speech файла openwar3.json); вызывать его может любая программа.

Заставить юнита говорить POST /api/say

{
  "inst": 16,
  "bubbles": [
    { "unit": "0x14A12614", "name": "Горный король", "text": "За мной, в атаку!" },
    { "unit": "0x14A12924", "name": "Архимаг", "text": "Сейчас будет снежная буря.",
      "style": { "font_px": 26, "text_color": "#FFE080", "bg_color": "#C0102040" } },
    { "screen": [960, 110], "key": 1, "name": "Рассказчик", "text": "Первая волна орков прибудет через 30 секунд.",
      "style": { "tail": false, "type_ms": 0 } },
    { "world": [-4684, 2644], "key": 2, "text": "Точка сбора", "style": { "font_px": 16 } }
  ]
}
ПолеОписание
unit / world / screenОдно из трёх: следовать за юнитом (если у него есть полоска здоровья — прямо над ней) / координаты карты / пиксели экрана (для закадрового текста)
nameИмя говорящего в первой строке — любое, не обязательно имя этого юнита
textТекст реплики, переносится автоматически
duration_msСколько показывать; 0 = автоматически 3 ~ 5 секунд
keyНомер облачка в мире / на экране: новое сообщение с тем же key заменяет старое
updateЕсли такое облачко уже есть — заменить только текст, не сбрасывая таймер (для потокового вывода)
stylefont_px, max_width_px, text_color, bg_color, border_color, tail, side_px, opacity, type_ms, font…

Одновременно — не больше 32 облачков; накладные расходы — в среднем около 0.1 ~ 0.2 ms за кадр.

Разговор с локальной моделью POST /api/chat

{
  "inst": 16, "unit": "0x14A12614", "name": "Горный король",
  "persona": "Ты играешь Мурадина, Горного короля из Warcraft: прямолинейный, любит выпить. Одна-две разговорные фразы, не длиннее 40 символов.",
  "message": "Впереди толпа огров. Атакуем или нет?",
  "stream": true
}

Возвращается {"reply": "...", "first_token_ms": 283, "total_ms": 342}, и к этому моменту ответ уже висит над этим юнитом. Каждый юнит помнит последние 6 обменов репликами.

Прочее

МетодОписание
GET /api/instancesЗапущенные игры
GET /api/units?inst=16&mine=true&heroes=trueСписок юнитов (с китайскими названиями, координатами, здоровьем)
POST /api/clearУбрать одно или все облачка
GET /api/llm, POST /api/llmПосмотреть / изменить настройки модели (base_url, model, max_tokens, temperature)
POST /api/banterПосиделки крестьян: рабочие на базе по очереди ворчат в образе своих персонажей, плюс объявление в начале матча (все данные о ходе игры — реальные)
POST /api/camtalkДиалоги в кадре: герои и их свита, попавшие в кадр, разговаривают в соответствии со своими ролями
POST /api/eventsРеакция на события: начало боя, конец боя, гибель героя, переход на следующий тир, потеря здания… говорят, только когда что-то случилось

Как выбрать локальную модель

Замеры на одной RTX 5090 (5 игровых реплик):

МодельВидеопамятьСкоростьОдна репликаВывод
Qwen3.6-35B-A3B (MoE, за раз активно только 3B), Q4, thinking выключен20.6 GB≈142 token/s≈0.3 s (первый токен ≈0.27 s)Рекомендуем: быстро, естественно отыгрывает роли на китайском
gpt-oss-20b (MXFP4), reasoning low11.3 GB≈280 token/s0.3 ~ 0.8 sЕсли мало видеопамяти; китайский чуть более пресный
Qwen3.6-27B (плотная), Q417.2 GB≈39 token/sчерез 5.5 s всё ещё думаетНе подходит для диалога в реальном времени
  • Скорость определяется числом параметров, активных за раз, а не общим числом: MoE на 35B активирует только 3B и работает в 3 ~ 4 раза быстрее плотной 27B.
  • «Размышления» обязательно выключить: иначе все токены уходят на размышления, и до ответа дело не доходит.
  • Облачко печатает около 22 символов в секунду, так что скорость генерации — уже не узкое место; по-настоящему на ощущения влияет задержка первого токена.

Чтобы реплики звучали «по-настоящему»

Передавайте модели только реальные данные о ходе игры (число матчей, победы и поражения, численность армии, запасы) и явно пишите «используй только эти факты». Проверено: без этого ограничения модель выдумывает бои, которых не было.