Идея выглядела разумно: отдавать простые задачи локальной модели, а дорогой облачный Codex беречь для сложного. Эксперимент с MCP-сервером и gemma3:4b показал обратное — маршрут через локальную модель потребовал вдвое больше облачного input на той же задаче. Вот что произошло и как это повторить без сюрпризов.
Зачем вообще это пробовать
Если у вас уже стоит Ollama с небольшой моделью, соблазн очевиден: зачем тратить лимит Codex на разбор короткого traceback, сводку лога или генерацию commit message? Пусть Codex делегирует рутину локально, а сам занимается архитектурой и ревью.
Для делегирования есть готовый механизм — MCP (Model Context Protocol). Codex поддерживает локальные STDIO MCP-серверы: процесс запускается на машине, читает JSON-RPC из stdin и возвращает результат через stdout. Конфигурация хранится в ~/.codex/config.toml.
Как собрать MCP-сервер для Ollama
Сервер получился компактным, без внешних Python-зависимостей. У него один инструмент — local_llm. Входящий запрос выглядит так:
{
"prompt": "Что означает эта ошибка?",
"context": "SMTPAuthenticationError: 535 authentication failed",
"mode": "classify",
"max_tokens": 160,
"temperature": 0
}В ответ приходят не только слова модели, но и метрики:
{
"answer": "Incorrect username or password for the email server",
"model": "gemma3:4b",
"wall_time_ms": 6098,
"prompt_tokens": 71,
"output_tokens": 44,
"tokens_per_second": 9.52
}Конфигурация в ~/.codex/config.toml:
[mcp_servers.ollama-worker]
command = "codex-ollama-worker"
default_tools_approval_mode = "approve"
[mcp_servers.ollama-worker.tools.local_llm]
approval_mode = "approve"
output_token_limit = 1200Без явного approval_mode = "approve" первый же вызов падает с ошибкой:
MCP tool call requires approval, but approval policy is neverЭто не баг — неинтерактивный процесс не может показать диалог подтверждения, поэтому Codex блокирует новый инструмент по умолчанию.
Что умеют gemma3:4b и gemma2:2b на реальных задачах
Восемь задач из обычного проекта: TLS hostname mismatch, SMTP 535, извлечение портов из Docker Compose, path traversal, логика записей в БД, защита от повторной рассылки, добавление UNIQUE с дублями, commit message. Все запуски с temperature=0.
| Модель | Время 8 задач | Медиана | Скорость | Сгенерировано |
|---|---|---|---|---|
| gemma3:4b | 147,1 с | 9,4 с | 9,40 ток/с | 1202 токена |
| gemma2:2b | 109,1 с | 13,8 с | 14,19 ток/с | 1328 токенов |
Обе модели работали на CPU — MacBook Pro 2019 с Radeon Pro 5300M (4 ГБ VRAM), но Ollama показывала 100% CPU. GPU не задействовался.
На механических задачах всё нормально: порты из Compose извлечены верно, Redis назван зависимостью, SMTP 535 объяснён, commit message написан.
На security review начались проблемы. Фрагмент с path traversal:
target = Path('/srv/uploads') / upload.filename
with target.open('wb') as output:
output.write(await upload.read())gemma3:4b упомянула directory traversal, но главным исправлением предложила заменить wb на w. Опасное имя файла осталось опасным, а загрузка бинарных файлов дополнительно сломалась. gemma2:2b правильно указала на upload.filename, но посоветовала Path.joinpath() — функция соединяет пути, но не обезвреживает ../../etc/passwd.
На задаче с логикой записей 4B-модель предложила перевести русское сообщение об ошибке на английский, проигнорировав настоящую проблему: запрос учитывает любую старую запись, и после первого занятия пользователь теряет возможность записываться навсегда.
Вывод по качеству: маленькие модели справляются с механической обработкой текста, но не с ревью безопасности и предметной логикой.
Куда исчезла экономия: разбор токенов
Одна и та же задача — объяснить SMTP 535 и назвать первую проверку — прогнана тремя маршрутами:
| Маршрут | Время | Облачный input | Облачный output | Локальные токены |
|---|---|---|---|---|
| Ollama напрямую | 6,1 с | 0 | 0 | 71 + 44 |
| Codex напрямую | 8,8 с | 14 309 | 49 | 0 |
| Codex → MCP → Ollama → Codex | 21,4 с | 29 301 | 384 | 71 + 44 |
Маршрут через MCP использовал примерно вдвое больше облачного input, чем прямой Codex. Причина структурная: локальная модель не заменяет облачный проход, а добавляет к нему дополнительные. Codex сначала читает запрос и решает вызвать инструмент — это один полный inference со всей историей. Потом читает ответ Gemma и формирует итог — ещё один. Каждый такой «поворот» тянет за собой весь накопленный контекст.
Это подтверждается известной проблемой Codex: каждый вызов инструмента возвращает управление модели и запускает новую полную инференцию с полной историей. В одном из публичных тредов 28 из 34 вызовов инструментов оказались связаны с ожиданием и опросом статуса. Промпт-кэширование Codex CLI чувствительно к порядку инструментов: стоит поменять конфигурацию или добавить новый MCP-сервер — кэш сбрасывается и стоимость растёт.
Где это ломается
Кэш слетает при любом изменении конфигурации. Добавили инструмент, поменяли порядок в config.toml — Codex пересчитывает всё с нуля. Экономия на кэше исчезает.
Polling убивает бюджет. Если задача требует нескольких обращений к инструменту (проверка статуса, итерация), каждый вызов — отдельный полный inference. На длинных цепочках расход токенов растёт нелинейно.
Маленькие модели галлюцинируют на безопасности. Результат выглядит убедительно, но содержит ошибки, которые сложно заметить без глубокого знания темы. Использовать вывод gemma3:4b как окончательный ревью — риск.
GPU может не подключиться. Ollama не всегда подхватывает дискретную видеокарту автоматически. Проверьте ollama ps — если видите 100% CPU при наличии GPU, нужна ручная настройка.
Что попробовать дальше
Схема имеет смысл для строго механических задач без итераций: извлечение структурированных данных из текста, форматирование, классификация с заранее известными категориями, генерация commit message по diff. Там один вызов — один ответ, без polling.
Если хочется реально сократить расход — стоит смотреть в сторону промпт-кэширования внутри самого Codex и минимизации числа tool call в сессии, а не в сторону делегирования через MCP.
FAQ
Почему MCP-маршрут дороже прямого Codex?
Каждый вызов инструмента через MCP — это отдельный полный inference Codex со всей историей диалога. Вместо одного прохода получается минимум два: решение вызвать инструмент и обработка его ответа. На коротких задачах накладные расходы превышают любую экономию от локального inference.
Можно ли заставить Ollama использовать GPU на Mac?
Ollama поддерживает Metal на Apple Silicon, но на Intel Mac с дискретной Radeon автоматического подключения GPU может не быть. Проверьте командой ollama ps — в колонке Processor должно быть GPU или GPU+CPU, а не 100% CPU.
ollama psДля каких задач локальная модель через MCP всё-таки окупается?
Для однократных механических преобразований без итераций: извлечение портов из конфига, классификация ошибки по коду, генерация commit message. Главное условие — один вызов инструмента на задачу, без polling и без цепочек.
Как разрешить вызов MCP-инструмента без диалога подтверждения?
В ~/.codex/config.toml нужно явно указать approval_mode = "approve" для конкретного инструмента. Без этого Codex заблокирует вызов с ошибкой approval policy is never.
Влияет ли порядок MCP-серверов в конфиге на расход токенов?
Да. Codex CLI использует промпт-кэширование, которое чувствительно к изменениям конфигурации. Если поменять порядок инструментов или добавить новый сервер, кэш сбрасывается и стоимость inference растёт до следующего «прогрева».
