От AnythingLLM до LibreChat, MCP и Gemma4
Эта статья про то как мы искали способ подключить языковую модель к своим рабочим данным, что пробовали, что не подошло и на каком стеке в итоге остановились. По ходу рассказа — практические шаги и команды, которых достаточно, чтобы повторить у себя тот же стенд: все адреса, пути и токены в примерах ниже условные, их нужно заменить на свои.
Постановка задачи
Отправная точка — рутина, знакомая каждому, кто пробовал обсуждать рабочие цифры с ИИ. Чтобы задать один вопрос по данным, нужно сделать выгрузку в Excel, приложить файл в чат и заново объяснить модели, что лежит в столбцах, за какой период срез и как считается каждая метрика. Следующий вопрос — и весь цикл повторяется: новая выгрузка, новый файл, новые объяснения.
Задача сводилась к тому, чтобы разорвать этот цикл: дать ИИ постоянный инструмент подключения к данным. Чтобы он сам читал схему кубов — измерения, меры, периоды — и отвечал точными числами, не правдоподобно звучащим текстом.
Отсюда — три требования, которые и определили весь дальнейший выбор инструментов:
• Прямой доступ: модель сама ходит в таблицы и сама читает их схему — без ручных выгрузок и без пересказа, что лежит в столбцах.
• Точность: в ответе допустимы только числа, реально полученные из данных, — а значит, обычного чата с LLM мало, нужен инструмент, который считает, не угадывает.
• Приватность: данные не уходят за периметр компании — ни выгрузками, ни через промпт, поэтому облачный API с чужими датацентрами отпадает сразу.
Открытая модель и локальный запуск: Gemma + Ollama
Первый шаг — определиться, на чём будет работать сама модель, прежде чем подключать к ней какие-либо данные.
Открытая модель против облачного API
Требование «данные не покидают периметр» автоматически исключает облачные чат-моделей: даже если сами данные не отправлять в промпт напрямую, работать с чувствительной аналитикой через внешний API — избыточный риск и лишняя договорная нагрузка. Открытая модель, которую можно запустить на своём железе, снимает этот вопрос полностью: веса модели один раз скачиваются и дальше живут внутри компании. Дополнительный практический плюс — отсутствие оплаты за токены на этапе эксперимента, когда количество запросов и итераций промпта заранее не ограничено.
Ollama: запуск в одну команду
Для запуска открытой модели нужен был инструмент, который не требует отдельной инженерной подготовки — GPU-кластера, ручной сборки инференс-сервера, конвертации весов. Ollama выбрали именно за это: одна команда поднимает модель локально и отдаёт наружу уже готовый OpenAI-совместимый HTTP API, который «из коробки» понимают и AnythingLLM, и LibreChat, и любой другой клиент, рассчитанный на OpenAI-формат.
На Linux и macOS Ollama ставится одной командой — скрипт сам скачивает и устанавливает бинарник и системный сервис; на Windows для этого есть обычный установщик с сайта ollama.com:
# Linux / macOS curl -fsSL https://ollama.com/install.sh | sh
Дальше нужно скачать саму модель — команда ollama pull тянет веса из библиотеки Ollama и кладёт их в локальный кэш:
ollama pull gemma4:e4b
После pull модель уже доступна по OpenAI-совместимому API на http://localhost:11434/v1/ — это можно сразу проверить обычным curl: если Ollama поднялась и модель на месте, в ответ придёт список доступных моделей в формате OpenAI:
curl http://localhost:11434/v1/models
Открытая модель от Google: Gemma4
Из доступных открытых моделей выбор остановился на Gemma по совокупности практических причин: у неё открытые веса, есть версии разного размера под разное железо (в том числе облегчённые варианты, которые ощутимо быстрее работают на обычном компьютере без выделенной видеокарты), и модель хорошо поддержана в экосистеме Ollama — то есть её можно было взять и запустить в тот же день, без доработок под конкретную инфраструктуру. Для первой проверки идеи взяли самую лёгкую версию — тег gemma4:e4b.
Данные нужны модели «по запросу»: MCP вместо RAG
Модель, которая просто отвечает текстом, — это ещё не решение задачи. Нужен был способ дать ей доступ к живым данным в OLAP-кубах.
MCP против RAG
Первая развилка — как вообще «подключать» данные к модели. Классический путь для такого рода задач — RAG: заранее нарезать данные на фрагменты, превратить их в эмбеддинги и подсовывать модели «похожие по смыслу» куски в контекст. Для отчётности и точных цифр этот подход плохо подходит по своей природе: «похожий по смыслу» кусок текста — не то же самое, что точное число за конкретный период по конкретной таблице. Протокол MCP (Model Context Protocol) решает другую задачу — он даёт модели набор инструментов, которые она может вызвать сама и получить точный результат на свой вопрос, не похожий фрагмент из индекса. Для сценария «спросить про метрику и получить точное число» это принципиально более подходящий механизм, поэтому выбор был сделан в его пользу с самого начала, до того как появился первый рабочий прототип.
XLTable и MCP-сервер
Роль источника данных в проекте играет XLTable — наш собственный продукт, семантический слой OLAP и XMLA-совместимый сервер, который связывает Excel с современными хранилищами данных. У нас он поднят над ClickHouse: аналитики работают с готовыми кубами — измерениями, мерами, иерархиями — вместо сырых таблиц и SQL.
У XLTable есть MCP-сервер, то есть тот самый мост, через который модель обращается к кубам напрямую. Ничего дописывать не пришлось — сервер подключается к любому MCP-клиенту и даёт модели достаточный набор инструментов:
• list_databases() — список баз (каталогов кубов); нужен только если баз несколько и то, с какой работать, ещё не известно из диалога;
• list_cubes(database) — список готовых OLAP-кубов. Отдельного инструмента для «сырых» таблиц больше нет — модель работает только с уже готовыми кубами;
• describe_cube(database, cube) — схема куба: измерения (с их уровнями иерархии) и меры. Обязателен перед каждым query_cube — это единственный источник точных, регистрозависимых имён;
• query_cube(database, context) — сам агрегирующий запрос: группировка по уровням измерений, агрегация мер, опциональный фильтр по строкам. В ответе — row_count и флаг truncated (по умолчанию отдаёт не больше 1000 строк, для большего лимит передаётся явно).
Если источник данных не XLTable, а что-то своё, принцип переносится без изменений: MCP-сервер — это процесс, который просто отдаёт модели набор вызываемых функций. Минимальный аналог на Python (пакет mcp) с тем же набором ролей — разведка схемы и запрос данных — выглядит так: функция list_tables отдаёт список доступных таблиц/кубов, а get_data возвращает сами данные по имени таблицы и параметрам фильтра; обе помечены декоратором @mcp.tool(), чтобы модель увидела их как вызываемые инструменты, а вызов mcp.run() в конце запускает сам сервер:
from mcp.server.fastmcp import FastMCP mcp = FastMCP("my-data-server") @mcp.tool() def list_tables() -> list[str]: """Список доступных таблиц/кубов.""" return ["sales", "customers"] @mcp.tool() def get_data(table: str, limit: int = 100) -> list[dict]: """Данные из таблицы (с фильтрами — по своей логике).""" ... if name == "__main__": mcp.run() # stdio-транспорт по умолчанию
Точный синтаксис запуска (mcp.run() и параметры транспорта) зависит от версии SDK — стоит сверяться с актуальной документацией протокола MCP. Такой сервер можно поднять локально (транспорт stdio, процесс запускается самим клиентом) или как отдельную службу с постоянным адресом (транспорт streamable-http или sse, клиент подключается по URL) — второй вариант понадобится позже, когда MCP-сервер, как и модель, переедет на отдельный сервер.
AnythingLLM: первый рабочий интерфейс
Модель есть, доступ к данным через MCP есть — не хватало интерфейса, через который это можно было бы попробовать в чате, не тратя время на написание своего фронтенда.
Чем подкупил AnythingLLM
AnythingLLM — простое десктопное приложение с чатом, задуманное ровно для того, чтобы дать модели доступ к своим документам и источникам. Для первой проверки идеи это оказалось идеальным сочетанием:
• установка в несколько кликов — обычный установщик под настольную ОС, без Docker, реверс-прокси и возни с окружением;
• Ollama подключается как источник модели в пару полей настроек — достаточно указать адрес локального эндпоинта;
• MCP-сервер добавляется почти так же просто: путь к процессу в конфигурации — и инструменты XLTable уже видны модели в чате.
Пошагово это выглядело так: сначала поставить AnythingLLM обычным установщиком под свою ОС; затем в настройках выбрать провайдера LLM — Ollama — и указать адрес (http://localhost:11434) и модель; после этого в настройках MCP-серверов (Agent Skills → MCP) добавить свой сервер — команду и аргументы для запуска процесса (тип stdio) либо URL, если сервер уже поднят как отдельная служба; и наконец открыть чат и задать вопрос по данным — модель должна сама вызвать инструмент MCP-сервера и ответить, опираясь на его результат.
В сумме это позволяло быстро проверить связку «модель + свои данные».
Что получилось
Связка заработала: модель через AnythingLLM видела инструменты XLTable и могла их вызывать, отвечая на простые вопросы по данным. Это подтвердило, что общая архитектура — открытая модель плюс MCP-сервер над кубами — жизнеспособна и стоит того, чтобы довести до продуктового вида.
Чего не хватило
Дальше начались ограничения. Главное из них — концептуальное: AnythingLLM остаётся чатом над источниками, а нам нужен был агент. То есть не собеседник, который иногда дёргает инструмент, а исполнитель, который сам выстраивает цепочку вызовов — уточнить схему куба, запросить данные, посчитать, собрать дашборд — и выдаёт готовый результат. Из этого выросли и все частные претензии:
• гибкость системного промпта агента — а именно в промпте нужно было закрепить жёсткие правила про источник каждого числа и протокол обработки ошибок, описанные выше;
• контроль над тем, какие инструменты и в каком порядке вызываются, и как обрабатывать ошибки вызова;
• возможность генерировать собственные визуальные артефакты — дашборды с графиками — прямо в ответе, не только текст;
• удобная работа с несколькими эндпоинтами моделей одновременно, когда нужно сравнивать варианты.
Каждое из этих ограничений напрямую упиралось в исходные требования к точности ответов. Продолжать на AnythingLLM не было смысла: нужна была платформа, на которой строят не чат, а агента.
Модель на сервере
Как только связка подтвердила свою работоспособность, локальный запуск на одном компьютере стал узким местом: модель занимала ресурс личной машины, была недоступна остальной команде и не гарантировала, что эндпоинт будет на месте, когда он нужен. Логичным шагом стал перенос Ollama на отдельный сервер с постоянным адресом — при этом локальный запуск не убрали, а оставили как отдельную «песочницу» для быстрых экспериментов, работающую параллельно с серверной моделью.
Подключение к серверу — обычный SSH с авторизацией по ключу (адрес сервера, путь к ключу и логин здесь и далее условные, подставьте свои):
ssh -i <путь к приватному ключу> <пользователь>@<адрес сервера>
Сама VM под Gemma4 — не самая мощная машина, но с GPU, чего достаточно для одной модели на команду:
|
Ресурс |
Значение |
|
Платформа |
Intel Ice Lake with NVIDIA® Tesla® T4 |
|
Количество GPU |
1 |
|
vCPU |
4 |
|
RAM |
16 ГБ |
|
Объём дискового пространства |
SSD 30 ГБ |
На сервере Ollama устанавливается той же командой, что и локально. На сервер осознанно перебрались не за самой Ollama, а за более сложной 12b-версией модели: она требовательнее к ресурсам и на личной машине уже работала на пределе. Но прежде чем закладывать серверные ресурсы именно под 12b, её стоило сравнить с лёгкой e4b на одном и том же железе — поэтому на сервер временно поставили обе версии и прогнали одинаковый тест на каждой (флаг —verbose показывает не только ответ, но и служебную статистику — время загрузки, скорость генерации):
curl -fsSL https://ollama.com/install.sh | sh ollama pull gemma4:e4b ollama run gemma4:e4b --verbose "тест" ollama pull gemma4:12b ollama run gemma4:12b --verbose "тест"
Сравнение подтвердило то же самое, что и в разделе про выбор модели ниже: 12b увереннее держит протокол и точнее вызывает инструменты. После этого лёгкую версию с сервера убрали — ollama rm удаляет скачанные веса и освобождает место на диске — и дальше сервер настраивали уже только под 12b:
ollama rm gemma4:e4b
Кастомная модель под задачу
На сервере завели не «сырую» 12b-модель, а отдельную версию, донастроенную конкретно под задачу работы с данными — gemma4-12b-xltable. Для неё зафиксировали параметры генерации — расширенный контекст, низкая температура и ограниченные top_p/top_k, чтобы модель меньше «фантазировала» по стилю генерации в дополнение к правилам самого промпта. То есть точность ответов на этом этапе стали закладывать не только в текст системного промпта, но и в параметры инференса.
Практически параметры зашиваются в модель через файл Modelfile — для этого сначала нужно создать сам Modelfile (значение num_ctx подбиралось опытным путём среди 16384 / 24576 / 32768, в зависимости от доступной памяти сервера):
cat > gemma4-12b-xltable.Modelfile << 'EOF' FROM gemma4:12b PARAMETER num_ctx 16384 EOF
а затем собрать из Modelfile именованную модель командой ollama create — имя после неё (gemma4-12b-xltable) и есть то имя, которое дальше указывается в клиентах вместо исходного тега:
ollama create gemma4-12b-xltable -f gemma4-12b-xltable.Modelfile
Значение num_ctx, зашитое в Modelfile, — это дефолт; при необходимости оно всё равно переопределяется на уровне запроса через addParams в librechat.yaml (см. ниже) — так одновременно поддерживаются оба слоя настройки контекста.
Проверить результат можно двумя командами: ollama show —parameters печатает параметры, реально зашитые в модель, а ollama ps показывает, какие модели прямо сейчас загружены в память сервера:
ollama show gemma4-12b-xltable --parameters ollama ps
Отдельная тема — сам сервис Ollama на сервере. По умолчанию он слушает только localhost и выгружает модель из памяти после нескольких минут простоя — и то, и другое имеет смысл поменять, но по-разному. Команда systemctl edit открывает редактор для override-файла сервиса без изменения основного юнита:
sudo systemctl edit ollama
В открывшийся override-файл добавляется блок с переменными окружения:
[Service]Environment="OLLAMA_LOAD_TIMEOUT=10m"Environment="OLLAMA_HOST=127.0.0.1:11434"Environment="OLLAMA_KEEP_ALIVE=-1"Environment="OLLAMA_NUM_CTX_CHECKPOINTS=5"
после чего изменения нужно применить: systemctl daemon-reload перечитывает конфигурацию systemd, а systemctl restart перезапускает сам сервис Ollama уже с новыми переменными:
sudo systemctl daemon-reloadsudo systemctl restart ollama
Смысл переменных:
OLLAMA_HOST=127.0.0.1:11434 — слушать только локальный интерфейс, не торчать наружу без авторизации; изначально Ollama слушала все интерфейсы (0.0.0.0), чтобы LibreChat с другой машины мог до неё достучаться напрямую, но так до модели мог достучаться и кто угодно, кто знал адрес сервера. Поэтому доступ снаружи закрыли на уровне самой Ollama и вместо этого поставили перед ней авторизующий reverse-proxy, который слушает публичный порт, проверяет API-ключ в заголовке запроса и только потом проксирует его на 127.0.0.1:11434 — тот же API-ключ указывается в custom-эндпоинте LibreChat (см. ниже). Сам reverse-proxy — обычный nginx: слушает публичный порт, сверяет заголовок Authorization с ожидаемым токеном и только при совпадении проксирует запрос на Ollama:
server { listen <порт reverse-proxy>; server_name ; location / { if ($httpauthorization != "Bearer <api-ключ reverse-proxy>") { return 401; } proxy_pass http://127.0.0.1:11434; proxy_set_header Host $host; proxy_read_timeout 300s; } }
OLLAMA_KEEP_ALIVE=-1 — не выгружать модель из памяти между запросами, иначе каждый запрос после паузы ждёт повторной загрузки весов, а это заметные секунды на 12b-модели; OLLAMA_LOAD_TIMEOUT=10m — увеличенный таймаут именно на загрузку модели (полезно для крупной версии на не самом быстром диске); OLLAMA_NUM_CTX_CHECKPOINTS=5 — сколько контекстных чекпоинтов кэшируется одновременно.
OLLAMA_KEEP_ALIVE=-1 удерживает модель в памяти между запросами, но после перезапуска самой VM или сервиса Ollama модель всё равно нужно загрузить с нуля при первом обращении — и первый пользователь ждёт этой загрузки. Чтобы модель прогревалась сама сразу после старта VM, не при первом живом запросе, добавили отдельный systemd-юнит. Сначала командой nano создаётся сам файл юнита:
sudo nano /etc/systemd/system/ollama-warmup.service
в него вписывается описание сервиса: он стартует после ollama.service и зависит от него (After/Requires), сначала в ExecStartPre ждёт, пока Ollama реально начнёт отвечать на /api/tags, а затем в ExecStart шлёт пустой запрос на генерацию с «keep_alive»: -1 — это не генерирует текст, а только грузит веса модели в память:
и в конце — снова daemon-reload, чтобы systemd подхватил новый юнит, и systemctl enable —now, которая и включает автозапуск, и сразу запускает юнит один раз:
sudo systemctl daemon-reloadsudo systemctl enable --now ollama-warmup
И на случай, когда нужно понять, что в конкретный момент делает Ollama — грузит модель, выгружает её из памяти или уже отвечает на запросы, — удобно смотреть лог сервиса в реальном времени (journalctl -f) с фильтром по ключевым словам:
sudo journalctl -u ollama -f | grep -E 'evicting|predicted|loading model|POST'
loading model — идёт загрузка весов, evicting — модель выгружается из памяти (сигнал, что OLLAMA_KEEP_ALIVE не сработал или его нет), predicted — сколько токенов сгенерировано за запрос, POST — сам факт обращения к API.
LibreChat: платформа для полноценного агента
LibreChat закрывал главное — на нём можно собрать именно агента: задать ему роль системным промптом, выдать набор инструментов и позволить самому решать, что и в каком порядке вызывать, прежде чем отвечать пользователю. Поверх этого сошлось сочетание возможностей: подробная конфигурация через один YAML-файл, возможность подключить несколько LLM-эндпоинтов одновременно и переключаться между ними в интерфейсе, встроенные Artifacts (генерация HTML-контента прямо в ответе — то, чего не хватало в AnythingLLM для дашбордов), собственный встроенный Run Code — отдельный от MCP инструмент выполнения кода, которым и закрывается требование считать цифры, не прикидывать их, — и полноценная поддержка MCP-серверов.
Разворачиваем LibreChat
Есть два рабочих варианта установки: штатный Docker (docker compose up -d поднимает сразу LibreChat, MongoDB и MeiliSearch одной командой) и напрямую через npm, без контейнеров — этот вариант проще для встраивания своих изменений на сервере, куда есть прямой доступ, и его развернули в этом стенде:
git clone https://github.com/danny-avila/LibreChat.gitcd LibreChatnvm install && nvm use # версия Node зафиксирована в .nvmrccp .env.example .env # секреты (CREDS_KEY, JWT_SECRET и т.д.) # заменить на свои, например через # openssl rand -hex 32cp librechat.example.yaml librechat.yaml
При запуске без Docker MongoDB нужно поднять отдельно (локально или использовать уже существующий) — адрес указывается в MONGO_URI в .env. Дальше — установка зависимостей и запуск:
npm cinpm run frontend # собирает клиентnpm run backend # прод; для разработки — npm run backend:dev
LibreChat поднимется на http://localhost:3080 (порт — переменная PORT в .env); на сервере процесс держат через pm2 или в tmux/screen-сессии, не в интерактивном шелле.
Конфигурация LibreChat
В librechat.yaml рядом настраиваются сразу два эндпоинта Ollama — локальный для разработки и серверный с уже донастроенными моделями и параметрами генерации. У локального эндпоинта apiKey — просто заглушка «ollama», потому что сама Ollama без reverse-proxy запросы не авторизует; у серверного — настоящий API-ключ, который проверяет reverse-proxy перед тем, как пропустить запрос к Ollama (LibreChat подставляет значение apiKey в заголовок Authorization сам, отдельно его прописывать не нужно), а baseURL смотрит не в порт Ollama, а в порт reverse-proxy:
endpoints: custom: - name: "Ollama" apiKey: "ollama" baseURL: "http://localhost:11434/v1/" models: default: ["gemma4:e4b"] fetch: true - name: "Ollama Server" apiKey: "<api-ключ reverse-proxy>" baseURL: "http://<адрес сервера>:<порт reverse-proxy>/v1/" models: default: ["gemma4-12b-xltable"] fetch: true addParams: num_ctx: 32768 temperature: 0.3 top_p: 0.9 top_k: 64 num_predict: 768 reasoning_effort: "low"
Такая конфигурация позволяет держать оба варианта под рукой и переключаться между ними в одном интерфейсе, не разворачивая отдельные инструменты под каждый вариант.
MCP-сервер XLTable подключается рядом, в том же файле — либо как локальный процесс, который LibreChat сам запускает через command и args (транспорт stdio):
mcpServers: xltable: type: stdio command: <путь к python.exe в venv> args: - <путь к mcp_server.py>
либо, когда MCP-сервер тоже переезжает на отдельный адрес, — как удалённая служба, к которой LibreChat подключается по URL (транспорт streamable-http):
mcpServers: xltable: type: streamable-http url: http://<адрес сервера>/mcp headers: Authorization: 'Bearer <токен>' timeout: 60000
Если Ollama или MCP-сервер работают на приватном/локальном адресе, не на публичном домене, дополнительно нужно разрешить его в блоке mcpSettings.allowedAddresses (для MCP) и endpoints.allowedAddresses (для custom-эндпоинтов) в том же librechat.yaml — иначе запрос будет заблокирован защитой от SSRF.
Системный промпт агента
Главной работой на этом этапе оказалось не подключение MCP, а системный промпт. Именно в нём задаются правила, которые не дают модели выдумывать данные. Центральное правило:
Каждое число в выводе обязано иметь источник: конкретный ответ от xltable в этом диалоге, либо расчёт над этими числами, реально выполненный через инструмент Run Code.
Вокруг него выстроен протокол, который позволяет агенту самостоятельно действовать в разных сценариях: вызывать инструменты, узнавать точные имена полей из ответа MCP-сервера, читать данные, строить таблицы и проверять расчёты, выполнив код. Отдельно описано и поведение при сбое: при ошибке вызова — перечитать схему куба, не угадывать написание поля во второй раз. Любая LLM склонна отвечать уверенно даже там, где данных для уверенного ответа нет — это общее свойство, а не недостаток конкретной модели, и лечится оно правилами в промпте и протоколом вызовов.
В интерфейсе LibreChat это собирается в разделе Agents: агенту задаётся системный промпт с правилами выше и подключаются нужные инструменты — свой MCP-сервер, execute_code (Run Code — для проверки расчётов кодом, не «в уме») и artifacts (для дашбордов в виде HTML прямо в ответе). Run Code выполняется отдельным сервисом, не самим LibreChat — вместо облачного API-ключа LibreChat в этом стенде используется самохостинг на основе ClickHouse code-interpreter; именно через него проходит проверка расчётов кодом, о которой говорит системный промпт агента.
Сам code-interpreter разворачивается отдельно от LibreChat, через свой docker-compose:
git clone https://github.com/ClickHouse/code-interpreter.gitcd code-interpreterdocker compose up --build -d
В репозитории есть несколько готовых compose-файлов под разные сценарии (локальная разработка, macOS, продуктовый масштабируемый вариант); для прода стоит переопределить CODEAPI_INTERNAL_SERVICE_TOKEN на реальный секрет — без него часть маршрутов работает без авторизации.
Skill с шаблонами дашбордов
Чтобы не описывать оформление каждого дашборда прямо в системном промпте, повторяющиеся шаблоны и формулы вынесли в отдельный skill — xltable-dashboards. Там хранятся готовые вёрстки под разные типы данных (тренд, сравнение, доли, сводная таблица) и формулы для типовых расчётов (процент роста, детекция выбросов, сравнение периодов), на которые системный промпт просто ссылается. Это разделение сделало сам промпт короче и оставило место для его дальнейшего роста без превращения в нечитаемую простыню правил. Подключается такой skill в конфигурации агента точно так же, как MCP-сервер и остальные инструменты — файлом с примерами, на который агент ссылается по имени.
Вживую это выглядит так: на вопрос про выручку и количество заказов по категориям агент сам вызывает query_cube в xltable и возвращает таблицу с точными числами, а на следующий шаг — «построй дашборд с этой таблицей, добавь график» — собирает через Artifacts готовый HTML-дашборд с KPI-карточками, графиком и той же таблицей, не переспрашивая источник данных заново:
Gemma4:e4b vs Gemma4:12b — что выбрать для такой задачи
После того как обе версии модели поработали какое-то время в связке с одним и тем же MCP-сервером и одним и тем же системным промптом, сложилась практическая картина различий:
|
|
gemma4:e4b |
gemma4-12b-xltable |
|
Где крутится |
Локально, обычный ПК |
Отдельный сервер |
|
Следование протоколу |
Слабее — чаще нарушает правило |
Стабильно держит строгий системный промпт |
|
Точность вызова инструментов |
Годится для черновой проверки цепочки MCP |
Увереннее подбирает точные имена полей |
|
Роль в стеке |
Разработка и обкатка промпта |
Рабочие сценарии для пользователей |
Вывод, к которому пришли на практике: e4b оставили как модель для разработки и обкатки промпта на локальной машине. Для рабочих сценариев, где важна точность цифр и дисциплинированное следование протоколу, на сервере оставили именно 12b-версию — она увереннее держит длинный и строгий промпт и точнее подбирает имена полей при вызове инструментов.
ollama run <модель> --verbose "тест"
на той же VM показал для e4b eval rate 45.03 tokens/s (520 токенов ответа за 11.55 с, prompt eval rate 6.48 tokens/s на 18 токенов промпта), для 12b — eval rate 21.88 tokens/s (295 токенов ответа за 13.48 с, prompt eval rate 14.41 tokens/s на 18 токенов промпта). Ответы разной длины, поэтому total duration напрямую не сравнить, а вот eval rate — сравнимая метрика: 12b генерирует примерно в два раза медленнее e4b, что ожидаемо для модели почти втрое крупнее.
Проверить это на своих данных просто — держать локальный и серверный эндпоинты рядом в одном librechat.yaml, как в примере выше, и сравнивать ответы на одних и тех же вопросах, прежде чем выбирать модель для продуктового использования.
Итоги
По результатам эксперимента сложился рабочий стек: открытая модель Gemma через Ollama, MCP-сервер XLTable над OLAP-кубами и LibreChat как платформа для агента с Artifacts, Run Code и гибкой конфигурацией системного промпта.
Ключевой вывод: открытые модели можно донастроить для работы с данными до состояния, когда им можно доверять точные цифры, — если выстроить вокруг них инженерию: протокол в системном промпте, обязательный источник для каждого числа и проверка расчётов через код вместо доверия на слово. С этим стеком удалось закрыть все три требования из постановки задачи: прямой доступ к данным без ручных выгрузок — через MCP; точность — через протокол и обязательные вызовы query_cube и Run Code вместо угадывания; приватность — модель и данные всё это время оставались на своей инфраструктуре, ни разу не покинув периметр компании.
AnythingLLM на старте оказался правильным выбором для быстрой проверки, но для дальнейшего развития не хватило расширенных возможностей. LibreChat дал именно тот уровень контроля — над промптом, параметрами модели, MCP-инструментами и визуальными артефактами одновременно — которого не хватало на предыдущем шаге.
ссылка на оригинал статьи https://habr.com/ru/articles/1074406/