ARCANADA
Все записи
Блог 7 апреля 2026

6 локальных LLM как бэкенд Claude Code: почему 4 из 6 не работают

6 локальных LLM как бэкенд Claude Code: почему 4 из 6 не работают

Почему это важно именно сейчас. Зависимость от больших облачных моделей — это единая точка отказа: квоты API меняются, цены растут, сервисы падают, регионы попадают под ограничения. Если ваш агентский workflow полностью зависит от удалённого API, вы в одном шаге от полной остановки. Тестирование локальных моделей — это не хобби, а операционная устойчивость.

Кроме того, Ollama только что выпустила релиз, специально оптимизированный под Apple M4/M5 Neural Engine и архитектуру единой памяти. Разрыв в производительности между локальным и облачным inference стремительно сокращается — и нам нужны жёсткие цифры, чтобы понять, где мы находимся сегодня.

Все 6 моделей пишут рабочий код. Все проходят тесты. Но когда ты подключаешь их как бэкенд к Claude Code — 4 из 6 ломаются. Одна модель диагностирует собственный баг, но не может его исправить. Другая игнорирует задачу и начинает настраивать тебе memory system. Третья изобретает несуществующие инструменты.

И только 1 из 6 реально пригодна для ежедневной работы.

Я протестировал 6 локальных LLM через Ollama 0.20.2 на MacBook Pro M4 Max 48GB. Одна задача, одинаковый промпт, два режима: прямая генерация через API и работа через Claude Code CLI как агентский бэкенд. Результаты уничтожают миф «большая модель = хороший агент».

1. Стенд и методология

Железо: MacBook Pro M4 Max, 48 GB Unified Memory, 16 ядер (12P + 4E)
Софт: Ollama v0.20.2, Claude Code CLI v2.1.85
Квантизация: Q4_K_M для всех моделей — стандартный Ollama default, оптимальный баланс качества и скорости
Настройки: num_ctx=32768, temperature=0, max_tokens=8192

Шесть моделей, три архитектуры:

МодельАрхитектураВсего / Активных параметровVRAMКонтекст
gemma4:latestDense8B11.6 GB128K
gemma4:26bMoE25.2B / 3.8B active21.4 GB256K
gemma4:31bDense30.7B29.8 GB256K
qwen3-coder:30bDense30.5B21.9 GB128K
qwen3.5:35b-a3bMoE35B / 3B active26.9 GB256K
glm-4.7-flashDense~30B22.5 GB128K

Задача: написать thread-safe LRU cache на Python с использованием OrderedDict, threading.Lock, type hints и docstrings. Плюс 5 pytest-тестов. Одинаковый промпт для всех моделей и обоих режимов — чтобы сравнение было честным.

Почему именно эта задача? Она требует: правильной структуры класса, потокобезопасности, обработки граничных случаев (capacity <= 0), и отдельного тестового файла с корректными импортами. Достаточно сложно, чтобы обнажить разницу между моделями, и достаточно просто, чтобы объективно проверить результат через pytest.

Два режима тестирования:

  1. Raw Ollama — прямой запрос к Ollama API. Модель получает промпт, генерирует код в markdown, я извлекаю его regex’ом и прогоняю pytest.
  2. Claude Code Agent — модель работает как бэкенд Claude Code CLI. Агент получает задачу и должен самостоятельно вызвать tool Write для создания файлов.
# Подключение модели к Claude Code
export ANTHROPIC_AUTH_TOKEN=ollama
export ANTHROPIC_BASE_URL=http://localhost:11434
claude --model gemma4:26b -p "Create lru_cache.py and test_lru_cache.py..."

2. Raw Ollama: все 6 моделей справляются

Первый сюрприз: при прямой генерации через API все 6 моделей пишут рабочий код с проходящими тестами. Разница в качестве между 8B и 30B моделями — минимальна. Разница в скорости — огромна.

МодельDecode tok/sPrefill tok/sTTFTTotalТестыVRAM
qwen3-coder:30b96.98910.27s14s5/521.9 GB
gemma4:latest (8B)84.11,2634.5s27s6/611.6 GB
gemma4:26b (MoE)77.01,06714.4s33s5/521.4 GB
glm-4.7-flash65.07280.34s21s5/522.5 GB
qwen3.5:35b-a3b (MoE)45.06510.39s44s10/1026.9 GB
gemma4:31b (Dense)19.719842.6s113s6/629.8 GB

Что бросается в глаза:

qwen3-coder:30b — абсолютный лидер по скорости. 97 tok/s, 14 секунд от промпта до готового кода. TTFT (Time to First Token) всего 0.27 секунды — ты начинаешь видеть ответ мгновенно.

gemma4:latest (8B) — лучший prefill (1,263 tok/s) и всего 11.6 GB VRAM. Для модели размером с флешку — 6/6 тестов впечатляют.

qwen3.5:35b-a3b — лучшее покрытие тестами: 10/10. Модель написала не 5, а 10 тестов, включая тест на thread safety и тест на TypeError при невалидном ключе. Но есть подвох.

Gotcha с qwen3.5: у неё по умолчанию включён thinking mode. Без параметра "think": false в API-запросе все токены уходят во внутренние рассуждения, а поле response приходит пустым. Я потратил два прогона, прежде чем понял — модель «думала» 8192 токена и не выдала ни одного символа кода. Исправляется одной строкой:

requests.post(f"{OLLAMA_BASE}/api/generate", json={
    "model": "qwen3.5:35b-a3b",
    "prompt": TASK_PROMPT,
    "think": False,  # Без этого — 0 полезных токенов!
    "stream": True,
    "options": {"num_ctx": 32768, "temperature": 0, "num_predict": 8192}
})

gemma4:31b — 20 tok/s, почти 2 минуты. TTFT 42.6 секунды — ты ждёшь 43 секунды, прежде чем увидишь первый символ ответа. При том что код ничем не лучше, чем у 8B модели. На raw-задачах 31b — пустая трата времени.

Вывод: для скриптовой генерации кода (без агентского протокола) все модели одинаково хороши. Выбирай по скорости и VRAM.

3. Claude Code Agent: 4 из 6 терпят крах

Второй режим: та же задача, но через Claude Code CLI. Модель больше не просто генерирует текст — она должна работать как агент. И вот тут начинается резня.

Что Claude Code ожидает от бэкенд-модели:

Claude Code CLI — это оркестратор. Он отправляет модели огромный системный промпт (тысячи токенов) с описанием доступных инструментов: Read, Write, Edit, Bash, Grep, Glob и другие. Модель должна ответить не текстом, а структурированным JSON с вызовом инструмента:

{
  "role": "assistant",
  "content": [
    {
      "type": "tool_use",
      "name": "Write",
      "input": {
        "file_path": "/tmp/lru_cache.py",
        "content": "import threading\nfrom collections import OrderedDict..."
      }
    }
  ]
}

Ключевые требования:

  • Имя инструмента должно быть точным: Write, не write_file, не create_file
  • Имена параметров должны совпадать: file_path, не path, не filename
  • Модель не должна пытаться выполнять действия сама — только запрашивать выполнение через tool_use
  • После получения результата tool_use модель должна продолжить диалог (multi-turn)

Результаты:

МодельВремяTurnsКодТестыПричина провала
gemma4:26b (MoE)67s1OK4/5Опечатки в тестах
gemma4:31b (Dense)380s1OK7/7— (perfect, но медленно)
gemma4:latest (8B)99s2FAIL0/0Не понимает протокол
qwen3-coder:30b193s3FAIL0/0Неверное имя инструмента
qwen3.5:35b-a3b225s3FAIL0/0Неверное имя параметра
glm-4.7-flash669s4FAIL0/0Полностью игнорирует задачу

Теперь — вскрытие каждого провала. С цитатами из реальных ответов моделей.


gemma4:latest (8B) — «Слишком мала, чтобы понять комнату»

“I see that you are attempting to use a tool... I cannot perform web searches as I do not have access to that functionality.”

8B модель не смогла удержать в контексте огромный системный промпт Claude Code и начала галлюцинировать. Она решила, что ей предлагают сделать веб-поиск. 82 токена за 2 turn’а — и сдалась.

Вердикт: capacity failure. 8B недостаточно для понимания агентского протокола.


qwen3-coder:30b — «Правильная идея, неверное имя»

“I apologize for the confusion. It seems there was an error with the tool I tried to use. The write_file tool is not available in the current tool set.”

Модель поняла, что нужно использовать инструменты. Но вызвала write_file вместо Write. Скорее всего, qwen3-coder обучалась на другом tool use schema (возможно, OpenAI function calling), где инструменты называются иначе. Claude Code отклонил вызов, модель попробовала ещё дважды — и сдалась.

Вердикт: tool name mismatch. Понимает концепцию, но знает другие имена.


qwen3.5:35b-a3b — «Самодиагностика собственного бага»

Это самый поразительный случай. Ответ модели (на русском!):

«Я вижу ошибку от предыдущего вызова инструмента. Похоже, что был передан неверный параметр (path вместо file_path).»

Модель сама поняла, в чём проблема. Она вызвала Write (правильное имя!) с параметром path (неправильное имя — нужен file_path). Claude Code отклонил вызов, и модель точно диагностировала причину. Но не смогла исправиться — потому что ошибка в её собственной генерации, а не в исправляемом коде.

Вердикт: parameter name mismatch. Ближе всего к успеху из всех провалившихся. Один маппинг path → file_path в прокси-сервере — и она бы заработала.


glm-4.7-flash — «Я тебе лучше memory настрою»

«Отлично! Файл памяти был успешно создан. Теперь система готова сохранять важную информацию о вас, ваших предпочтениях и контексте работы. Вы можете сохранять следующие типы памяти: user, feedback, project, reference...»

Модель прочитала системный промпт Claude Code (в котором есть блок про auto memory) и решила, что её просят настроить систему памяти. Полностью проигнорировала задачу про LRU cache. 4 turn’а, 4089 токенов, 669 секунд — самый дорогой и самый бесполезный прогон.

Вердикт: task comprehension failure. Не поняла, что от неё хотят.

4. Два выживших: gemma4:26b и gemma4:31b

Из шести моделей через Claude Code agent protocol прошли только две. Обе — Gemma 4. Не случайность: Google специально тренировала Gemma 4 для agentic tasks.

gemma4:26b MoE — практический выбор

  • 67 секунд, 1 turn, 4 из 5 тестов
  • Код lru_cache.py — корректный, чистый, с docstrings и type hints
  • Проблема: в тестовом файле на строке 44 написано LRUCCache вместо LRUCache. И на строке 17 — LRLRCache. Опечатки в повторяющихся буквенных последовательностях — характерный артефакт MoE-архитектуры, где routing между экспертами иногда «заикается» на похожих токенах
  • Основной код (lru_cache.py) безупречен — ошибки только в тестах
  • 21.4 GB VRAM — комфортно в 48 GB, остаётся место для IDE и браузера

gemma4:31b Dense — идеальный, но медленный

  • 380 секунд (6.3 минуты!), 1 turn, 7 из 7 тестов
  • Единственная модель, написавшая тест на потокобезопасность без явной просьбы
  • Идеально чистый код без артефактов
  • Но 380 секунд — это не агентская работа. Это ожидание. В реальном сценарии, где ты итерируешь по 20-30 запросов за сессию, это превращается в часы простоя
  • TTFT 42.6 секунды означает, что ты ждёшь почти минуту, прежде чем увидишь первый символ

Ключевой insight: 31b — лучший кодер. 26b — лучший агент. Агентская работа — latency-sensitive. Не важно, насколько идеален код, если ты ждёшь 6 минут на каждый запрос.

5. Пост-обработка: regex вместо ретрая

Опечатки gemma4:26b — не приговор. Один regex превращает 4/5 в 5/5:

import re

def fix_moe_typos(code: str) -> str:
    """Fix common MoE routing artifacts in class names."""
    # LRUCCache, LRLRCache, LRUUCache → LRUCache
    return re.sub(r'LR[A-Z]*Cache', 'LRUCache', code)

Проверено: после применения этого regex все 5 тестов проходят.

Это подводит к более общей философии: если модель делает 95% правильно, детерминированный постпроцессор дешевле повторного inference. Один прогон gemma4:26b = 67 секунд и ~1160 output-токенов. Regex = 0 секунд и 0 токенов. Выбор очевиден.

В бенчмарк-скрипте (quick_bench_v2.py) уже встроен пайплайн постобработки:

  1. Получить JSON-ответ от Claude Code CLI
  2. Извлечь поле result (текстовый ответ модели)
  3. Regex’ом найти все ```python``` блоки
  4. Классифицировать: блок с class LRU*lru_cache.py, блок с def test_test_lru_cache.py
  5. Записать файлы
  6. Прогнать pytest
def extract_code_blocks(text: str) -> tuple[str, str]:
    """Extract lru_cache.py and test_lru_cache.py from markdown."""
    # Handle unclosed code blocks (model hit token limit)
    blocks = re.findall(r"""```(?:python|py)?\s*\n(.*?)(?:```|$)""", text, re.DOTALL)

    lru_code, test_code = "", ""
    for block in blocks:
        block = block.strip()
        if "class LRU" in block or "OrderedDict" in block:
            lru_code = block
        elif "def test_" in block or "import pytest" in block:
            test_code = block
    return lru_code, test_code

Важная деталь: (?:```|$) в regex — это обработка незакрытых code-блоков. Если модель упирается в лимит токенов и не успевает закрыть ```, regex всё равно извлечёт код. Без этого qwen3.5 и gemma4:31b теряли бы весь output на длинных генерациях.

6. Таксономия провалов: 6 уровней агентской совместимости

Проанализировав все 6 прогонов, я вижу чёткую иерархию провалов. Каждая модель «отваливается» на определённом уровне — и это не бинарная ситуация «работает/не работает»:

УровеньМодельЧто происходитИсправимо?
1. Task comprehension failureglm-4.7-flashИгнорирует задачу, делает своёНет
2. Tool hallucinationgemma4:latest (8B)Выдумывает несуществующие инструментыНет
3. Tool name mismatchqwen3-coder:30bВызывает write_file вместо WriteДа, прокси
4. Parameter name mismatchqwen3.5:35b-a3bpath вместо file_pathДа, прокси
5. Output corruptiongemma4:26bОпечатки LRUCCacheДа, regex
6. Successgemma4:31bВсё правильно

Уровни 1-2 — фатальные. Модель не понимает, что от неё хотят, и никакой прокси это не исправит.

Уровни 3-4 — самые интересные. Модели понимают концепцию tool use, но обучались на другой schema. Прокси-сервер между Claude Code и Ollama, который ремаппит write_file → Write и path → file_path, мог бы спасти и qwen3-coder, и qwen3.5. Это вполне посильная инженерная задача.

Уровень 5 — тривиально исправляется постпроцессором.

Уровень 6 — всё работает, но ценой скорости.

7. Почему именно Gemma 4

Из 6 моделей агентский протокол осилили только две — и обе Gemma 4. Это не совпадение.

Google при анонсе Gemma 4 специально выделяла agentic capabilities: нативная поддержка tool calling, обучение на agentic workflows, оптимизация для multi-turn взаимодействий. В отличие от qwen3-coder (оптимизирована для code completion) или glm-4.7-flash (оптимизирована для быстрого inference), Gemma 4 тренировалась именно на том сценарии, который тестирует Claude Code.

Почему MoE побеждает Dense:

gemma4:26b MoE активирует только 3.8B параметров из 25.2B на каждом forward pass. Это даёт:

  • Скорость генерации 77 tok/s (vs 20 tok/s у 31b Dense с 30.7B активных параметров)
  • Достаточно интеллекта для понимания tool use schema
  • 21.4 GB VRAM — помещается в 48 GB с запасом

Парадокс: модель с 3.8B активных параметров работает как агент, а модель с 30.5B активных (qwen3-coder) — нет. Потому что дело не в объёме параметров, а в том, на каких данных они обучены.

gemma4:26b — Goldilocks:

  • Достаточно быстрая (67 секунд на задачу) для итеративной работы
  • Достаточно умная (понимает Claude Code protocol) для агентских задач
  • Достаточно компактная (21.4 GB) для повседневного ноутбука

8. Практические рекомендации

Профиль A: «Хочу локальный Claude Code agent»

Модель: gemma4:26b
Запуск: ollama launch claude --model gemma4:26b
Постпроцессинг: regex для MoE-опечаток

Чего ожидать: ~67 секунд на задачу (vs ~5 секунд с настоящим Claude через API). Код рабочий, но иногда нужна ручная доводка тестов. Подходит для: приватная разработка, офлайн, обучение, эксперименты. Не подходит для: продакшен-скорость, сложный мульти-файловый рефакторинг.

Конфигурация Ollama для бенчмарков:

export OLLAMA_NUM_GPU=99           # Все слои на GPU
export OLLAMA_KEEP_ALIVE=-1        # Не выгружать модель
export OLLAMA_NUM_PARALLEL=1       # Один запрос для стабильности
export OLLAMA_FLASH_ATTENTION=1    # Flash attention если поддерживается

Профиль B: «Просто хочу быструю генерацию кода»

Модель: qwen3-coder:30b (скорость) или gemma4:latest 8B (компактность)
Режим: прямой API Ollama, без Claude Code
Помни: "think": false для qwen3.5

Для автоматизации — Python + requests:

import requests
r = requests.post("http://localhost:11434/api/generate", json={
    "model": "qwen3-coder:30b",
    "prompt": "Write a Python function...",
    "think": False,
    "stream": False,
    "options": {"temperature": 0, "num_predict": 4096}
})
print(r.json()["response"])

Профиль C: «Хочу собрать middleware для других моделей»

Самый интересный вариант. qwen3.5 и qwen3-coder почти работают. Прокси между Claude Code и Ollama, который:

  1. Перехватывает tool_use от модели
  2. Ремаппит write_file → Write, read_file → Read
  3. Ремаппит path → file_path, filename → file_path
  4. Пробрасывает исправленный запрос в Claude Code

Это разблокирует минимум 2 дополнительные модели. qwen3.5 с её 10/10 тестами в raw режиме и 256K контекстом — особенно лакомый кандидат.

9. Что дальше

Разрыв между «может писать код» и «может быть агентом» будет сокращаться. Модели следующего поколения (Gemma 5, Qwen 4) наверняка будут тренироваться на tool use schema Anthropic наравне с OpenAI — рынок слишком большой, чтобы его игнорировать.

Ollama launch claude делает подключение тривиальным — бутылочное горлышко теперь в качестве моделей, а не в инфраструктуре.

Middleware-подход (ремаппинг tool names и parameter names) — это low-hanging fruit, который прямо сейчас может спасти 2-3 модели. Возможно, этим стоит заняться.

Но главный вопрос — скорость. Даже «рабочая» gemma4:26b отдаёт результат за 67 секунд. Настоящий Claude через API — за 5. Разница в 13 раз. Для однократной генерации это терпимо. Для итеративной агентской работы с 20-30 запросами за сессию — это разница между «продуктивный день» и «день ожидания».

Локальные модели как агентский бэкенд — работающая, но нишевая история. Для приватности, офлайна, экспериментов — отлично. Для продакшен-продуктивности — пока нет.


Методология: все данные получены на реальном стенде. Скрипт бенчмарка (quick_bench_v2.py) опубликован и воспроизводим. Каждое утверждение проверено результатами прогонов, сохранёнными в JSON. Цитаты из ответов моделей — дословные, из файлов raw_response.txt.