Последние полтора года наша ML-команда делает крайне интересные штуки, например строит ИИ-агентов поверх PostgreSQL, при этом инфраструктура меняется, индустрияПоследние полтора года наша ML-команда делает крайне интересные штуки, например строит ИИ-агентов поверх PostgreSQL, при этом инфраструктура меняется, индустрия

Выбор LLM и фреймворка для ИИ-агентов

defccd9ce27ae8c484d6b8cacf944921.png

Последние полтора года наша ML-команда делает крайне интересные штуки, например строит ИИ-агентов поверх PostgreSQL, при этом инфраструктура меняется, индустрия созревает, а ожидания и требования к качеству систем на базе ИИ только растут. Начинали мы с одной A100 в VK Cloud и довольно скромных задач, по типу «давайте прикрутим RAG», а сейчас одновременно поддерживаем прод AskPostgres, строим агентов-аналитиков, проводим эксперименты, разворачиваем собственные бенчмарки для агентов и готовимся к переезду на сервер с 8×H200, где уже можно запускать Qwen3-235B и разворачивать серьёзные работы по дообучению небольших моделей.

В статье о том, какие задачи мы решаем, как эволюционировало железо, модели и фреймворки, и почему в итоге мы гораздо меньше волнуемся какая модель лучше, и гораздо больше — о контекст-менеджменте и архитектуре агентов. Всё описанное актуально на конец 2025 — начало 2026 года.

Задачи, под которые мы строили стек

Вокруг единой LLM-инфраструктуры у нас постепенно вырос целый набор приложений. Чтобы было понятно, о чём речь, перечислю основные направления.

  1. Классический RAG Fusion над документацией и прочими источниками.
    Мы индексируем документацию Postgres Pro, статьи, вопросно-ответные пары, SQL-примеры, книги и прочий текст в pgvector. В качестве эмбеддера используем BGE-M3 и работаем не с BM25, а со sparse-векторами: строим разреженные представления текстов, считаем dense-векторы и комбинируем всё это через перемножение, ранжирование, дисконтирование и дополнительные эвристики. Обработка некоторых неструктурированных источников частично делегирована LLM.

  2. Развитие RAG в ИИ-агента на MCP с веб-поиском и SQL-executor.
    Классический чат с документацией быстро перестал устраивать: пользователям нужен полноценный ассистент с чёткой ролевой моделью и множеством разных инструментов. Поэтому вокруг RAG вырос ReAct-агент, который через Model Context Protocol (MCP) умеет ходить в PostgreSQL, обращаться к веб-поиску и комбинировать эти источники в одной цепочке рассуждений.

  3. ИИ-агент для аналитики.
    Это интерфейс поверх аналитических данных: агент понимает вопросы в духе «покажи выручку по регионам за последний год», генерирует SQL, объясняет результат, умеет возвращаться к прошлым шагам, строить графики и постепенно наращивать сложность запросов.

  4. Graph-RAG-агент над большой кодовой базой на C (ИИ-Copilot).
    Ядро PostgreSQL и сопутствующие проекты — это миллионы строк C-кода. Мы строим граф знаний в Apache AGE: вершины — директории, файлы, функции, структуры, макросы, переменные; рёбра — вложенные зависимости и вызовы. Агент поверх этого графа отвечает на вопросы о том, где реализовано вот это поведение, чем чревато изменение такого-то участка и помогает с навигацией по коду как по базе знаний, а не как по сплошному тексту с поиском.

  5. SQL-генератор.
    Развёрнутый пайплайн text-to-SQL, который мы тренируем и оцениваем на основе EX/EM-метрик и бенчмарков типа Spider/BIRD плюс наши схемы. Для обучения использовали Qwen-0.6B и на ней отрабатывали GRPO/SFT; отдельно экспериментировали с QLoRA-дообучением моделей порядка Qwen2.5-14B на чистом SFT.

  6. Генератор hint-сетов для PostgreSQL.
    Здесь в центре всего расширение pg_hint_plan. Модель видит запрос, статистику, индексы и предлагает подсказки в формате hint-сетов. Обучаем это дело при помощи GRPO, параллельно зажимаем формат подсказок формальной грамматикой, чтобы pg_hint_plan мог их распарсить и применить.

  7. Генератор схем БД и бизнес-логики из DSL.
    Мы используем DSL (domain specific language), который описывает не только сущности, но и схему транзакций для мутации схемы БД. Сама схема представляется как набор агрегатов с иерархической структурой данных в формате JTD (JSON Typedef). Агент по этому описанию генерирует таблицы, связи, транзакционную обвязку и фрагменты бизнес-логики, придерживаясь JTD-спецификации.

  8. Генератор тестовых данных.
    Под капотом это, казалось бы, «простой» генератор INSERT, но с нюансами. Агент умеет подцеплять правдоподобные списки значений (города, имена, адреса), учитывать связи между таблицами. Всё выходное описание генерации зажато в формальной грамматике, чтобы потом легко было валидировать и воспроизводить сценарии.

  9. Суммаризация и структурирование тикетов техподдержки.
    Цель здесь — не просто пересказать переписку, а разложить её в понятный отчёт: кто что делал, какие гипотезы проверялись, какие команды выполнялись, к какому итогу пришли. Параллельно агент классифицирует тикет по существующим меткам.

  10. Просмотр больших объёмов кода в поисках подозрительных фрагментов.
    По сути, LLM просеивает большие массивы кода и помечает фрагменты, которые выглядят подозрительно по заданным критериям. Это инструмент ускорения ручного аудита, а не его замена.

  11. Собственные бенчмарки для агентов.
    Мы пришли к схеме «тестируемый агент + тестирующий агент + валидатор». Первый решает задачу так, как будет делать это в проде, взаимодействуя с инструментами. Второй играет роль пользователя: отвечает, уточняет, направляет, опираясь только на ограниченное подмножество контекстных знаний. Третий, валидатор, анализирует весь диалог и решает, достигнута ли цель и соблюдены ли условия тестового кейса. Так мы тестируем не IQ модели, а поведение системы целиком.

  12. Исследования памяти и формальных ограничений.
    Параллельно мы играемся с тем, как устроить краткосрочную и долгосрочную память агентов, как управлять контекстом (от простого окна до суммарзации, маскирования и графов) и как формальные грамматики и строгий structured output влияют на качество, скорость и устойчивость поведения.

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

Первая итерация стека: 1×A100, RAG, LangChain и Ollama → vLLM

История с ресурсами у нас довольно классическая. Сначала была одна A100 80 GB в VK Cloud, на которой нужно было и экспериментировать, и что-то показывать по продукту. Этого хватало, чтобы поднимать 30–70-миллиардные модели в квантизации Q6_K_M через Ollama, крутить простые RAG-кейсы и собирать первые прототипы SQL-генератора.

Потом появился внутренний сервер с двумя A100. Это сильно облегчило жизнь: на нём сейчас живёт прод AskPostgres, существует несколько сред, там же мы держим стабильные пайплайны и параллельно гоняем эксперименты. В этот момент стало понятно, что Ollama — отличная штука для быстрого старта, но для прода нам нужен более контролируемый и эффективный движок инференса. Так мы пришли к vLLM.

Но почему именно vLLM, а не TensorRT-LLM или SGLang, у которых пиковая производительность как будто бы выше?

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

https://www.clarifai.com/blog/comparing-sglang-vllm-and-tensorrt-llm-with-gpt-oss-120b)
https://www.clarifai.com/blog/comparing-sglang-vllm-and-tensorrt-llm-with-gpt-oss-120b)
LLM inference engines performance testing: SGLang VS. vLLM | by Chirawat  Chitpakdee | Medium
https://medium.com/@occlubssk/llm-inference-engines-performance-testing-sglang-vs-vllm-cfd2a597852a
Изображение
https://x.com/vllm_project/status/1913513173342392596/photo/1

Короче, типичная война исследователей, использующих бенчмарки, как оружие. Таким образом, на выбор будет влиять не циферка или столбик в каком-то графике, а применимость фреймворка в нашем конкретном кейсе.

Что нам нужно? Нужна поддержка структурированного аутпута и контекстно-свободных грамматик — здесь все три движка подходят. Также нужна поддержка абсолютно разных моделей с Hugginf Face, потому что хочется пробовать много разных вариантов для подбора лучшей базы для агентов — здесь TensorRT-LLM начинает проигрывать, поскольку поддерживает ограниченный набор линеек и архитектур, а также требует процесса компиляции модели при её переключении, что тормозит и усложняет R&D. Ещё хочется поддержку инференса на CPU, что есть и в vLLM, и в SGLang, но нет в TensorRT-LLM. По итогу, финальный выбор между vLLM и SGLang был основан на объективной величине активного комьюнити — vLLM победил.

Сейчас мы ожидаем сервер с 8×H200, который уже можно считать полноценной площадкой для самых тяжёлых моделей. Там вполне реалистично запускать Qwen3-235B и соседей по размеру, а заодно развернуть систематическую активность по обучению маленьких моделей под специфичные задачи: свои SQL/Cypher/DSL-генераторы, семантические классификаторы, векторизаторы, реранкеры, суммаризаторы, анализаторы логов и так далее. На A100 этого тоже можно было добиться, но ценой довольно агрессивных компромиссов по квантизации, размерам модели и времени обучения.

Что касается фреймворков, начинали мы с максимально простого набора: LangChain, LangGraph, LangFuse, RAGAS и несколько моделей семейства Qwen2.5. Нас интересовали русский и английский, поэтому мы быстро отмели части LLaMA- и Gemma-линеек: по нашим задачам и корпусам они заметно проигрывали Qwen именно на русском языке, а промптинг с инструктированием под них шёл сложнее. В тот момент нас устраивала схема: LangChain для RAG, LangGraph для первых агентных графов на базе Qwen. На этом стекe мы подняли ChatPPG (прототип AskPostgres), обкатали базовый RAG, SQL-генератор и первые экспериментальные пайплайны «генератор + критик + валидатор». RAGAS соответственно использовали для оценки итеративных изменений в нашем RAG: как с точки зрения изменения пайплайна/промптов/источников данных, так и с точки зрения изменения базовой модели. Целевыми метриками выступали answer_relevancy и answer_corectness — ориентируясь на них, мы и принимали решения о том, повлияло изменения на работу системы, стало лучше или хуже:

f087599e067eceeeb8c8f3300ced65e8.jpg

Параллельно оформился подход к retrieval: вместо привычного тандема BM25 + dense-вектора мы сделали ставку на BGE-M3 с его dense + sparse-представлениями и начали работать со sparse-векторами как с полнотекстовой частью поиска. Для наших текстов это оказалось удобнее и проще в эксплуатации.

SQL-генератор и hint-сеты: где дообучение действительно оправдано

«Мы сделали Fine-tuning модели» — звучит гордо. Однако легко забыть, что для большинства прикладных задач дообучение больших моделей — скорее роскошь, чем необходимость. Наш практический опыт здесь довольно приземлённый.

Для 80-миллиардного класса (и тем более для моделей уровня Qwen3-235B) типичный набор задач: RAG, SQL, код, инструменты — уже решается «из коробки». 90% проблем устраняет:

  • нормальный контекст-менеджмент;

  • адекватный RAG (BGE-M3, хорошая индексация, грамотный парсинг);

  • архитектурой пайплайн (генератор, критик, валидатор, SGR);

  • жёсткий structured output.

Дообучение у нас оправдано в двух случаях. Первый — когда нужна небольшая модель под конкретный сценарий, например, on-prem у клиента, где нет мощных GPU. Второй — когда требуется очень специфичный формат вывода, который трудно стабильно выжать из модели одними промптами.

Технически мы используем классический набор: пробовали PEFT/LoRA/QLoRA, LLaMA-Factory и LMPO, но пришли де-факто к стандарту — TRL для экспериментов с SFT и RL-подходами (GRPO, GSPO и тому подобное). При этом для сложных вещей вроде SQL-генератора мы предпочитаем обкатывать методологию на маленьких моделях (например, на Qwen-0.6B), а уже потом переносить отобранные траектории и данные на более крупные.

Главный фильтр, который мы проходим, очень простой: если у нас нет мощностей, адекватного бенчмарка с метриками или данных для задачи, мы не дообучаем модель под эту задачу. Сначала тюнинг через контекст, а потом fine-tuning.

SQL-генератор стал первым местом, где мы подумали: «Ладно, здесь без дообучения будет больно». Нам хотелось, чтобы модель не просто выдавала валидный SQL, но и вела себя предсказуемо, понимала особенности диалекта PostgreSQL и делала какой-никакой schema-linking в процессе ризонинга на типичных запросах пользователей.

В качестве теста мы взяли Qwen-0.6B: на нём запускали SFT и GRPO с LoRA и учились конструировать reward. Это повысило качество при сложных запросах и показало, что подобного рода пайплайны оправданы на маленьких моделях. Также учили и Qwen2.5-14B с QLoRA на чистом SFT. В результате получили улучшение формата, но знаний новых модели это не добавило, что явно отображалось на бенчмарках.

Генератор hint-сетов вырос из этой же логики, но с более жёсткими ограничениями. Здесь в центре внимания pg_hint_plan, и любая ошибка в формате подсказки может привести к тому, что планировщик не поймёт, чего от него хотят, и просто проигнорирует план. Поэтому мы сразу зажали выход модели формальной грамматикой и тренировали её с помощью GRPO, максимально аккуратно подбирая наборы подсказок, которые реально ускоряют запросы.

Стоит отметить, что алгоритмов RL море и у каждого свои плюсы и минусы. Так, например, мы пробовали относительно новый GSPO и поняли, что он больше подходит для MoE-архитектур, в то время как GRPO на маленьких dense-моделях сходился быстрее.

DSL, JTD и генерация тестовых данных

Отдельная ветка — работа с DSL. Нам нужен был язык описания, в котором не только схема данных задаётся декларативно, но и схема транзакций для её мутации: какие операции возможны, какие инварианты должны сохраняться, какие агрегаты существуют. Для этого мы описали структуру в терминах JTD (JSON Typedef): так получилось связать иерархические агрегаты, бизнес-инварианты и транзакционную логику. Агент, который работает с этим DSL, по сути, выступает переводчиком с языка бизнеса на формальный язык, может задать уточняющие вопросы и создать необходимую мутацию в случае необходимости. Подробнее про сам DSL можно почитать здесь.

Ещё один более приземлённый пайплайн — генератор тестовых данных. Он получает схему и набор требований, а на выходе выдаёт набор INSERT. При необходимости агент подтягивает правдоподобные значения (списки городов, имён, доменов и тому подобное), следит за связями и edge-кейсами. Всё, что он выдаёт, тоже ограничено формальной грамматикой, так что генерацию можно всегда считать синтаксически корректной и валидной для дальнейших этапов работы с тестовой БД.

От «зоопарка» пайплайнов к агентам на MCP

Пока все эти компоненты жили каждый сам по себе, жизнь была непростой. Для RAG — свой пайплайн, для SQL — свой, для Graph-RAG по коду — ещё какой-то. Каждый держит собственные подключения к базе, свои форматы логов и свои представления о том, что такое контекст.

Переход к MCP дал долгожданный порядок как в инфраструктуре, так и в головах. Стало гораздо легче объяснять, как агенты работают с инструментами, какие существуют уровни абстракции и какие возможности расширения существуют. Мы стали описывать доступ к данным и инструментам в терминах MCP-серверов, а поверх них строить ReAct-агентов. В результате у нас появился единый слой, где:

  • PostgreSQL, файловая система, web-поиск, graph-store и внутренние API выглядят одинаково — как набор инструментов с чёткими контрактами;

  • разные агенты могут использовать один и тот же набор MCP-серверов без копипаста и «зоопарка» интеграций.

9a98ffd1102aa8c3870a1be24c9dfb42.png

На этом фоне особенно важным стало поведение модели именно как агента: насколько аккуратно она вызывает инструменты, как реагирует на ошибки, умеет ли честно признать, что нужных данных нет. Для начала можно воспользоваться лидербордами, по типу этого, но нужно же оценивать качество и на своих задачах. Это и подтолкнуло нас к созданию собственного бенчмарка с тройкой «тестируемый агент — тестирующий агент — валидатор». Нам оказалось мало просто «хорошей модели», нужна была система, которая ведёт себя предсказуемо в длинной траектории решения.

Текущий стек: vLLM, Qwen3-Next-80B и свой агентный слой

К настоящему моменту стек выглядит примерно так.

Внизу лежит vLLM как основной движок для инференса open-source-моделей. Далее идёт OpenAI-совместимый MCP-клиент, который занимается стримингом, подключением к MCP-серверам, управляет циклом агента и отвечает за то, чтобы все нужные инструменты были вызваны и весь контекст корректно передался в LLM, как мы хотим. Тут мы сознательно ушли от попыток строить сложные сценарии целиком внутри LangChain/LangGraph, оставив их для быстрого прототипирования. Причина есть: сырость и наличие большого количества багов в продовых сценариях. Логирование и аналитику мы тоже сделали свою на PostgreSQL. Ранее использовали LangFuse, но до тех пор, пока не обнаружили утечку памяти, что благополучно клало нам прод несколько раз в неделю.

5fa7284ddad6a0c8cc894c6479dcce34.png

Стоит отметить, что ресурсы между командами мы делим через LiteLLM: это удобный gateway с OpenAI-совместимым API, который умеет маршрутизировать запросы на разные модели - как локальные, так и внешние закрытые модели при необходимости.

Инструменты и источники данных живут в отдельном слое MCP-серверов. Это даёт возможность подключать и отключать серверы без переписывания логики агентов и делиться ими между разными продуктами/командами.

В описанной конфигурации по цифрам нагрузочного тестирования в многопользовательском режиме получаем в среднем 18 секунд на ответ с использование инструмента поиска по документации, а p95 при этом около 60 секунд, что более чем хорошо для нашего контекста.

Квантизация и её характер

Если говорить о квантизации, то на практике всё свелось к весьма простой картине.

Пока мы жили на A100, AWQ-4bit стал рабочей нормой для больших моделей. В таком виде Qwen3-Next-80B-A3B помещается на карту, показывает достойное качество на наших задачах и позволяет ещё строить поверх неё многоступенчатых агентов, не раздувая латентность до неприличия.

В более агрессивные режимы мы иногда заходили, но быстро убедились, что стоимость ошибок возрастает: модель чаще уводит вывод на неправильный язык, зацикливается на шаблонных фразах и чаще ломает формат вызова инструментов (сейчас иногда это тоже происходит). Часть этого лечится промптами и настройкой параметров семплирования (температура, top-p, top-k), но по-настоящему надёжной становится связка только тогда, когда вы сверху накрываете модель формальными грамматиками и жёсткими спецификациями выходных форматов.

С приходом H200 мы будем активнее смотреть в сторону FP8-квантов и более крупных моделей, но философия вряд ли изменится: квантизация — это trade-off между качеством и скоростью. Мы будем выбирать опираясь на бенчмаркинг и несколько измеряемых критериев, не исключая latency.

Что мы поняли про выбор моделей

Если попробовать выжать весь опыт в один тезис, он будет таким: модель важна, но контекст и архитектура ещё важнее.

Да, если взять gpt-5.2 или Gemini Pro 3.0, они объективно будут умнее любого открытого аналога. Но если агент не умеет управлять контекстом, если у него нет памяти и продуманной работы с инструментами, то никакая «мега-триллионная-модель» не спасёт ситуацию.

Мы прошли путь от Qwen2.5-14B/32B в Q6_K_M через Ollama до Qwen3-Next-80B-A3B AWQ-4bit на vLLM и сейчас смотрим в сторону Qwen3-235B на H200. На каждом шаге по пути можно было построить полезного агента, при условии что вокруг модели есть всё остальное: RAG, MCP, агентный слой, память, грамматики и бенчмарки.

Поэтому сейчас, выбирая LLM под новую задачу, мы в первую очередь спрашиваем себя, не о том, какая модель в прайме. Нас интересует:

  • какие данные и источники контекста нам нужны;

  • как мы будем управлять этим контекстом;

  • какие инструменты и в каком виде нужны агенту;

  • как мы будем измерять качество его поведения.

А модель — это просто компонент, который встраивается в эту систему. Пусть даже это будет gpt-5.2: без контекста и архитектуры ничего не поедет

Источник

Возможности рынка
Логотип Large Language Model
Large Language Model Курс (LLM)
$0.0003168
$0.0003168$0.0003168
-2.94%
USD
График цены Large Language Model (LLM) в реальном времени
Отказ от ответственности: Статьи, размещенные на этом веб-сайте, взяты из общедоступных источников и предоставляются исключительно в информационных целях. Они не обязательно отражают точку зрения MEXC. Все права принадлежат первоисточникам. Если вы считаете, что какой-либо контент нарушает права третьих лиц, пожалуйста, обратитесь по адресу service@support.mexc.com для его удаления. MEXC не дает никаких гарантий в отношении точности, полноты или своевременности контента и не несет ответственности за любые действия, предпринятые на основе предоставленной информации. Контент не является финансовой, юридической или иной профессиональной консультацией и не должен рассматриваться как рекомендация или одобрение со стороны MEXC.