Bollywood HungamaBREAKING: Jailer 2 ends with the promise of third part; Hrithik Roshan's massy special appearance has a Chulbul Pandey connection (Spoilers ahead)ESPNTransfer rumors, news: Liverpool eye move for Juventus' KaluluCNN TürkBakan Çiftçi duyurdu: 48 il için sarı, 5 il için turuncu kodlu uyarı!The Jerusalem Post'Mother of all bombs': US potential Iran strike plans include largest non-nuclear weapon - reportPunchWhy I stepped away from football – SouthgateESPN DeportesBrewers gana a Dodgers y lo tiene a un triunfo de la barrida한겨레국가 지원 ‘청년 첫 연금’도 양극화…사전신청 70% “부모가 대신”InquirerSenators deny 2027 budget used to pressure them to convict Sara DuterteThe South AfricanOrlando Pirates injury news: Nduli, De Jong, Mbatha, DlaminiUOLEleitores com dificuldade de locomoção podem pedir transporte gratuito para votar; veja comoZDF heuteEntdecken Sie das ZDF-NachrichtenstudioGIGAZINESakana ChatにAIの記憶を可視化する新機能「My Fish」が登場、覚えている内容や記憶同士のつながりを確認可能に
The Daily Newsstand · Free, Always
Thursday, October 15, 2026

Как я подружил llama-server с Claude Code: доработка Go-лаунчера для локальных LLM

Translate

В своих прошлых материалах я уже рассказывал о запуске крупных MoE-моделей на ограниченном железе через llama-server и о сравнительных экспериментах с 35B, 120B и 176B-классами моделей. Практика показала, что llama.cpp давно перестал быть просто «запускатором GGUF»: сегодня это полноценный рантайм с OpenAI-совместимым API, поддержкой инструментов, Jinja-шаблонов, спекулятивного декодирования и гибкого CPU/GPU-offload. Единственное, чего ему не хватало для повседневной работы, — удобной обвязки, которая избавила бы от ручного набора десятка флагов при каждом старте.

Именно поэтому у меня появился небольшой лаунчер на Go: llama-runner, который брал на себя типовые сценарии: сам раскрывал ~ в абсолютные пути, проверял наличие бинарника и модели, собирал аргументы llama-server, пробрасывал логи и корректно гасил дочерние процессы по Ctrl+C. Программа успешно решала свою задачу, пока я не наткнулся на статью «70 токенов в секунду в Qwen3.8-27B на RTX 3090», где помимо тонкой настройки llama-server подробно разбиралась связка локального сервера с агентной оболочкой Claude Code. Идея показалась мне логичным развитием собственного инструмента: зачем запускать сервер и клиент двумя командами в разных терминалах, если это можно сделать одним вызовом?

Так родилась вторая версия llama-runner, о которой и пойдёт речь.

Что именно требовалось сделать

Задача формулировалась просто: llama-runner должен по умолчанию запускать llama-server, дожидаться его готовности и стартовать Claude Code с уже настроенными переменными окружения. Так, чтобы пользователь видел перед собой интерфейс агента, а не возился с адресами и портами. При этом хотелось сохранить и «старое» поведение: если запуск идёт только ради сервера (например, для доступа через WebUI), Claude Code поднимать не нужно.

Формально это вылилось в четыре изменения:

  1. Флаг -server , это режим «только сервер», полностью совпадающий с прежним поведением.

  2. Флаг -alias с автоподстановкой значения local, если он не задан явно и не активен -server.

  3. Ожидание готовности llama-server через опрос /health с настраиваемым таймаутом.

  4. Запуск Claude Code с переменными ANTHROPIC_BASE_URL, ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY и ANTHROPIC_DEFAULT_*_MODEL.

Звучит несложно, но в процессе всплыли три нетривиальные проблемы, каждая из которых заслуживает отдельного абзаца.

Проблема первая: имя модели

Claude Code как известно, клиент, изначально рассчитанный на модели Anthropic, и по умолчанию он запрашивает у сервера claude-sonnet-4 или claude-haiku-4. Локальный llama-server о таких именах ничего не знает и отвечает 404 model not found. Возможных решений два: либо глобально прописать нужные переменные в ~/.bashrc, либо заставить лаунчер подставлять имя модели автоматически.

Я выбрал второй путь, но с оговоркой. Флаг -alias пробрасывается в llama-server как --alias <name> и одновременно устанавливается в три переменные: ANTHROPIC_DEFAULT_SONNET_MODEL, ANTHROPIC_DEFAULT_HAIKU_MODEL и ANTHROPIC_DEFAULT_OPUS_MODEL. Клиент и сервер договариваются об имени без участия пользователя. Чтобы не заставлять людей каждый раз вводить -alias, я предусмотрел автоподстановку значения local: если пользователь не задал флаг явно и не запустил лаунчер в режиме -server, alias выставляется автоматически. Проверка «задал ли пользователь флаг» делается через flag.Visit, так можно отличить «не задан» от «задан пустой строкой» (последнее означает сознательный отказ от алиаса).

Проблема вторая: логи сервера ломают TUI Claude Code

Это оказалось самым неприятным сюрпризом. Claude Code терминальное приложение с альтернативным экраном, невидимым курсором и постоянной перерисовкой ANSI-последовательностями. Как только llama-server пишет что-то в тот же терминал (а он пишет довольно много: метрики слотов, timing, предупреждения), интерфейс агента начинает «размазываться»: текст накладывается друг на друга, курсор прыгает, ввод глючит.

Решение этой проблемы заключается в том, что бы перенаправлять stdout и stderr сервера в файл, когда мы работаем в паре с Claude Code. В llama-runner появились флаги -log-file (по умолчанию ~/.cache/llama-runner/llama-server.log) и -quiet-server (по умолчанию true). В режиме -server логи, как и раньше, идут в терминал и там это никому не мешает. Туда же уходят логи mcp-search-server, если он запущен через -webui-mcp-proxy.

Проблема третья: шаблон чата Qwen и системное сообщение

Самая коварная ошибка. После запуска Claude Code в логах llama-server начал появляться 500 с текстом:

Error: Jinja Exception: System message must be at the beginning.

Как выяснилось, причина в стандартном Jinja-шаблоне Qwen 3.8, который строго требует, чтобы системное сообщение было первым в диалоге. Claude Code, когда делает повторную попытку после сбоя, отправляет системное сообщение уже не первым и шаблон падает, клиент "ретраит", цикл повторяется. В логе это выглядит как бесконечная серия W srv operator(): got exception.

Лечится подменой шаблона. Сообщество давно поддерживает исправленные версии, например, в репозитории froggeric/Qwen-Fixed-Chat-Templates на Hugging Face. Шаблон лежит в корне репозитория под именем chat_template.jinja, и скачивается одной командой через curl:

curl -fL -o ~/GGUF/qwen-fixed-chat-template.jinja \
  https://huggingface.co/froggeric/Qwen-Fixed-Chat-Templates/resolve/main/chat_template.jinja

Я специально кладу файл в ~/GGUF туда же, где лежат сами модели. Это не обязательно, но удобно: все связанные с моделями артефакты в одном месте, и путь к шаблону совпадает с дефолтом лаунчера. Флаг -f заставляет curl завершиться с ошибкой, если сервер вернёт не 200, а, например, HTML-страницу редиректа или заглушку блокировки.

После скачивания обязательно убедитесь, что получен именно Jinja-шаблон, а не что-то другое. Достаточно взглянуть на первые строки (пример актуален для моей конфигурации):

head -20 ~/GGUF/qwen-fixed-chat-template.jinja

Ожидаемый вывод, это конструкции вида {%- if tools %} и {{- '<|im_start|>system\n' }}. Если вместо этого вы видите <!DOCTYPE html> или JSON с полем error, значит скачалась заглушка, необходимо перепроверить URL и повторить загрузку. Полезно также сразу убедиться, что в файле нет строки, из-за которой падал сервер:

grep -n "System message must be at the beginning" ~/GGUF/qwen-fixed-chat-template.jinja

Пустой вывод grep это хороший знак: это именно исправленный шаблон, в котором проверка ослаблена. Если строка найдётся, значит скачался не тот файл (например, из другой ветки репозитория), и стоит поискать альтернативу (шаблон от Qwen2.5 или Qwen3-Coder).

В llama-runner появился флаг -chat-template с дефолтом ~/GGUF/qwen-fixed-chat-template.jinja. То есть после скачивания в указанное место вам больше ничего не нужно передавать вручную: лаунчер сам подставит --chat-template-file в аргументы llama-server. Если же файл лежит в другом каталоге или вы экспериментируете с несколькими шаблонами, путь можно переопределить:

./llama-runner -chat-template ~/models/another-template.jinja

Проверка существования файла сделана мягкой. Если по указанному пути шаблона нет, llama-runner выведет предупреждение вида предупреждение: файл шаблона "..." не найден, запускаю без --chat-template-file и продолжит работу, при этом сервер стартует со встроенным шаблоном модели. Это удобно, когда вы пробуете разные модели и не хотите каждый раз править конфигурацию: несовместимость шаблона с конкретной моделью видна сразу по ошибке 500 в логе, но сам лаунчер из-за отсутствия файла не падает. Сознательно отказаться от кастомного шаблона можно и явно, передав пустое значение:

./llama-runner -chat-template ""

Такой запуск полностью игнорирует и дефолт, и переменные окружения, и передаёт серверу управление шаблоном «как есть».

Что умеет новая версия

Собрав всё вместе, я получил инструмент со следующей логикой.

Режим по умолчанию (сервер + Claude Code).

./llama-runner

Лаунчер запускает llama-server с моделью из ~/GGUF, подставляет --alias local, подхватывает исправленный шаблон, ждёт готовности через /health, выставляет переменные окружения и стартует claude. Логи сервера уходят в ~/.cache/llama-runner/llama-server.log. Когда пользователь выходит из Claude Code (любым способом — /exit, Ctrl+C, EOF), лаунчер шлёт SIGINT серверу и дожидается его завершения. Порт не остаётся занятым «висящим» процессом.

Режим только сервера (старое поведение).

./llama-runner -server

Логи идут в терминал, Claude Code не запускается, поведение полностью совпадает с первой версией программы. Удобно, когда вы работаете через встроенный WebUI llama-server или подключаете собственный клиент.

Гибкая настройка.

./llama-runner -alias qwen3-coder -claude-dir ~/my-project
./llama-runner -model ~/GGUF/other.gguf -alias other
./llama-runner -chat-template ""          # без кастомного шаблона
./llama-runner -v                         # показать команду без запуска

Помимо этого, есть флаги для контекста (-c), размеров батча (-b, -ub), flash attention (-fa), числа параллельных слотов (--parallel), режима инструментов (-tools), Jinja-шаблонов (-jinja), включения mmap с ленивым режимом (-lm-lazy) и MCP-прокси (-webui-mcp-proxy). В режиме прокси лаунчер дополнительно поднимает mcp-search-server на порту :8090 , который осуществляет поиск необходимой информации в интернете и корректно гасит его при выходе. Код моего "поискового" MCP сервера.

Приглашение к использованию

Код открыт и доступен по адресу: https://github.com/SkalaSkalolaz/llama-runner/

Проект распространяется свободно, без каких-либо ограничений на использование. Если у вас Ubuntu или другой Linux, есть локальная модель в GGUF и вы давно хотели попробовать агентную работу с Claude Code поверх llama-server , берите и пользуйтесь. Сборка тривиальная:

git clone https://github.com/SkalaSkalolaz/llama-runner/
cd server
go build -o llama-runner .
./llama-runner -h

Особенно рекомендую тем, кто, как и я, экспериментирует с MoE-моделями на ограниченной VRAM: связка llama-server + Claude Code + Qwen3.8-35B-A3B даёт вполне рабочую агентную среду без облаков, API-ключей и ежемесячных платежей. Если найдёте баги, присылайте issue или пул-реквесты - проект живой, и я готов его развивать дальше.

Одна оговорка: llama-server с флагом --tools all это серьёзная штука. Если вы открываете порт не только на 127.0.0.1, ограничьте режим инструментов через -tools до нужного подмножества. В остальном - запускайте, экспериментируйте и делитесь результатами.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.