惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Security Latest
Security Latest
量子位
博客园 - 三生石上(FineUI控件)
小众软件
小众软件
S
SegmentFault 最新的问题
The GitHub Blog
The GitHub Blog
AWS News Blog
AWS News Blog
T
Threat Research - Cisco Blogs
博客园 - Franky
Vercel News
Vercel News
H
Help Net Security
Martin Fowler
Martin Fowler
Security Archives - TechRepublic
Security Archives - TechRepublic
L
LINUX DO - 热门话题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
Cyberwarzone
Cyberwarzone
W
WeLiveSecurity
V2EX - 技术
V2EX - 技术
C
CERT Recently Published Vulnerability Notes
S
Secure Thoughts
C
Cyber Attacks, Cyber Crime and Cyber Security
B
Blog RSS Feed
H
Hacker News: Front Page
P
Proofpoint News Feed
博客园 - 聂微东
N
News and Events Feed by Topic
C
Cybersecurity and Infrastructure Security Agency CISA
D
Docker
博客园_首页
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
人人都是产品经理
人人都是产品经理
The Hacker News
The Hacker News
S
Security @ Cisco Blogs
博客园 - 【当耐特】
F
Fortinet All Blogs
The Register - Security
The Register - Security
A
About on SuperTechFans
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Schneier on Security
NISL@THU
NISL@THU
Attack and Defense Labs
Attack and Defense Labs
Help Net Security
Help Net Security
Cisco Talos Blog
Cisco Talos Blog
月光博客
月光博客
IT之家
IT之家
有赞技术团队
有赞技术团队
Know Your Adversary
Know Your Adversary
Hugging Face - Blog
Hugging Face - Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
От Naive RAG до ReAct-агента: как мы строили корпоративного AI-помощника на open-source моделях (часть 1)
chestny_znak · 2026-05-19 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение15 мин

Охват и читатели267

Обзор

Привет, Хабр!

Меня зовут Константинов Александр, я — старший AI-инженер в Лаборатории искусственного интеллекта «Честного знака». Наша команда развивает «Честного помощника» — мультиагентную LLM-систему для обработки документов, поиска информации по Confluence, Jira, GitLab и генерации текстов. Главная цель команды — повышать эффективность и качество работы сотрудников за счёт расширения числа специализированных агентов в нашей мультиагентной системе. Но давайте будем честны: мы начинали с решения совсем другой задачи. Терминология на тот момент была ещё сырой и непроверенной, рынок open-source решений оставлял желать лучшего — мы выжимали максимум из того, что было доступно. Поэтому дальнейший рассказ будет полезен широкой аудитории: от тех, кто только начинает разбираться в теме, до руководителей отделов, которые хотят внедрить подобное решение у себя.

Это первая часть из двух. Здесь мы разберём ключевые понятия, а затем подробно пройдём весь путь создания MVP на базе Naive RAG — от индексации документов до первых ответов в продакшене и осознания, что этого недостаточно. Во второй части перейдём к Advanced RAG, мультиагентной архитектуре, ReAct-агенту и качественному тестированию.

Основная терминология и понятия

LLM — это категория «фундаментальных» моделей, обученных на огромных объёмах данных, что позволяет им понимать и генерировать естественный язык и другие виды контента, а также выполнять широкий спектр задач.

Токен – это единица текста, например слово или часть слова, которой присвоен уникальный числовой идентификатор. LLM-модели используют такие числовые представления, чтобы эффективно обрабатывать и «понимать» текст. Для расчета токенов используется модель-токенизатор от LLM, которая преобразует текст в токены.

Контекст — это набор токенов, которые LLM обрабатывает за один проход вперёд (forward pass). Он формирует основу для ответа модели.

Контекстное окно — это максимальное количество токенов, которое модель способна воспринять за один раз. Ограничение длины окна влияет на то, сколько информации можно включить в один запрос.

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

Open source LLM — это модели, которые доступны для свободного использования и модификации. Они не имеют лицензионных ограничений и могут быть использованы в коммерческих проектах. Наиболее популярные open source LLM:

  • Qwen

  • Deepseek

  • Mistral (отдам дань уважения :D)

Давайте подробнее остановимся на проблеме галлюцинаций. Представим ситуацию: пользователь задаёт вопрос по своей доменной специфике, которая отражена лишь на закрытой странице в Confluence. Что произойдёт?

LLM сгенерирует ответ, но он не будет соответствовать действительности — у модели нет доступа к этой информации.

Самое неприятное: чтобы понять, что модель галлюцинирует, нужно самому обладать доменной экспертизой. Но тогда зачем вообще спрашивать у LLM, которая не имеет доступа к релевантной информации?

Тогда возникает вопрос, как избежать галлюцинаций и расширить спектр знаний LLM? Для этого существует ряд подходов:

  1. Fine-tuning LLM на конкретной доменной задаче.

  2. RAG (Retrieval-Augmented Generation).

  3. Prompt Engineering

Сравнение подходов: Prompt Engineering, Fine-tuning и RAG в борьбе с галлюцинациями

Сравнение подходов: Prompt Engineering, Fine-tuning и RAG в борьбе с галлюцинациями

RAG (Retrieval-Augmented Generation) — подход, при котором перед генерацией ответа модель сначала извлекает релевантную информацию из внешней базы знаний, а затем формирует ответ на основе найденных данных. Благодаря подключению внешнего хранилища знаний нет необходимости переобучать всю модель под каждую конкретную задачу. RAG особенно эффективен для задач, требующих актуальных или узкоспециализированных знаний. Подробнее архитектуру разберем в следующих разделах.

Prompt Engineering — это процесс создания и оптимизации текстовых запросов для модели, чтобы получить более точные и релевантные ответы. Этот подход позволяет управлять поведением модели, настраивая её на конкретные задачи и данные.

Fine-tuning LLM — это процесс дообучения предобученной модели на конкретном наборе данных. Позволяет «зашить» в модель новые знания или стиль ответов, однако требует значительных вычислительных ресурсов и времени.

Коротко о плюсах и минусах каждого подхода:

Критерий

Prompt Engineering

RAG

Fine-tuning

Сложность внедрения

Низкая

Средняя

Высокая

Стоимость

Минимальная

Средняя (хранилище + inference)

Высокая (GPU, датасет)

Актуальность знаний

Ограничена контекстным окном

Высокая — данные обновляются независимо от модели

Низкая — требует повторного обучения при изменениях

Контроль над источниками

Нет

Да — можно отследить, из каких документов взят ответ

Нет

Качество на доменных задачах

Среднее

Высокое

Высокое

Риск галлюцинаций

Высокий

Низкий — ответ опирается на конкретные документы

Средний

Когда применять

Быстрые эксперименты, общие задачи

Корпоративные базы знаний, FAQ, поиск по документам

Специфический стиль ответов, узкая предметная область без внешних данных

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

Именно поэтому мы решили развивать наше решение в направлении RAG и перешли к первому этапу разработки MVP- Naive RAG.

Naive RAG

Naive RAG — это простейший подход к RAG, который не использует никаких дополнительных технологий и просто использует базу знаний в качестве источника информации и LLM для генерации ответа.

Состоит из 3 шагов:

1 шаг - Indexing — подготовка и загрузка документов в базу знаний. Включает три подэтапа

  • Parsing — извлечение текста из исходных документов (PDF, DOCX, Confluence-страницы и др.). На выходе получаем «сырой» текст без форматирования.

  • Chunking — разбивка извлечённого текста на небольшие фрагменты (чанки). Необходимо, потому что LLM имеет ограниченное контекстное окно и не может обработать целый документ за один раз. Размер чанка — типично от 256 до 1024 токенов.

  • Embedding — каждый чанк преобразуется в числовой вектор с помощью модели-эмбеддера. Этот вектор отражает смысловое содержание фрагмента. Все векторы сохраняются в векторную базу данных (в нашем случае — Qdrant).

Скрытый текст

Что такое модель-эмбеддер?

Это отдельная нейронная сеть, задача которой — превратить текст в вектор фиксированной размерности (например, 768 или 1024 числа). В отличие от LLM, эмбеддер не генерирует текст — он только «понимает» смысл и кодирует его в числа. Близкие по смыслу тексты получают близкие векторы, далёкие по смыслу — далёкие. Именно это свойство и лежит в основе семантического поиска в RAG. Среди популярных открытых моделей — intfloat/multilingual-e5-large, Qwen/Qwen3-Embedding-0.6B, deepvk/USER-bge-m3. Для русского языка важно выбирать мультиязычные или специально обученные на кириллице модели — иначе качество поиска резко падает.

  • Векторы бывают двух типов:

    • Dense (плотные) — вектор фиксированной длины, где каждое из сотен или тысяч чисел несёт смысловую нагрузку. Генерируются нейросетевыми эмбеддерами. Хорошо улавливают семантику: находят похожие по смыслу фрагменты, даже если слова в запросе и документе разные. Основной тип, используемый в RAG.

    • Sparse (разреженные) — вектор огромной размерности (десятки тысяч), где большинство значений равны нулю, а ненулевые позиции соответствуют конкретным словам из словаря. По принципу работы близки к классическому поиску по ключевым словам (например, BM25). Хуже справляются с синонимами, но точнее находят точные совпадения терминов, аббревиатур и имён собственных.

Пример работы эмбеддера

Пример работы эмбеддера

2 шаг - Retrieval — поиск релевантных фрагментов по запросу пользователя. Запрос так же преобразуется в вектор тем же эмбеддером, после чего в векторной БД находятся чанки с наименьшим «расстоянием» до вектора запроса — то есть наиболее близкие по смыслу.

3 шаг - Generation — найденные чанки передаются в LLM вместе с исходным вопросом пользователя. Модель формирует финальный ответ, опираясь на этот контекст, а не на свои «внутренние» знания.

Naive RAG

Naive RAG

Давайте разберём, как была устроена архитектура нашего Naive RAG MVP.

MVP состоял из двух независимых бэкендов: один отвечал за взаимодействие с LLM, второй — за работу с векторной базой данных Qdrant. Никакой оркестрации, никаких агентов — просто два сервиса, которые последовательно обрабатывали запрос пользователя.

Parsing + Chunking

В качестве источника знаний мы использовали Confluence. Для его загрузки применяли готовый Confluence Loader из библиотеки LangChain — он забирает страницы по API и возвращает текст. Далее текст разбивался на чанки с помощью Recursive Character Splitter: сплиттер рекурсивно делит текст по иерархии разделителей (\n\n → \n → . → ), стараясь сохранять смысловые границы абзацев.

Итерация 1: как делали — и почему это не работало

На старте мы запускали лоадер в самом простом виде, не задумываясь о параметре content_format — он остался на дефолтном значении ContentFormat.STORAGE:

from langchain_community.document_loaders import ConfluenceLoader
from langchain_community.document_loaders.confluence import ContentFormat

loader = ConfluenceLoader(
    url="https://confluence.yoursite.com/",
    token="<your-token>",
    space_key="MYSPACE",
    # content_format не указан → по умолчанию ContentFormat.STORAGE
)
documents = loader.load()

Сплиттер — аналогично: взяли большой chunk_size в символах, чтобы «не терять контекст»:

from langchain_text_splitters import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=4096,  # символы, не токены — это важно
    chunk_overlap=0,
)
chunks = text_splitter.create_documents([doc.page_content for doc in documents])

Здесь сразу две проблемы:

  1. ContentFormat.STORAGE — это сырое XML-представление страницы Confluence для внутреннего хранения. В нём макросы не отрендерены: таблицы, блоки кода, вставки дат, упоминания пользователей и другие макросы существуют как XML-теги, а не как текст. При парсинге BeautifulSoup вырезает все теги — и вместе с ними уходит весь контент макросов. В индекс попадает неполный текст без структуры.

    ContentFormat.VIEW, напротив, возвращает страницу в том виде, в каком её видит пользователь в браузере: макросы отрендерены, таблицы раскрыты, даты и имена стоят как обычный текст.

  2. chunk_size=4096 в символах — это не токены. Большинство эмбеддеров (например, multilingual-e5-large) имеют контекстное окно 512 токенов. Чанк из 4096 символов может содержать 2000–3000 токенов — модель молча обрезает всё, что не влезает, и векторизует только начало фрагмента. Качество поиска деградирует незаметно.

Итерация 2: как исправили

Для лоадера явно указали ContentFormat.VIEW и включили конвертацию в Markdown:

from langchain_community.document_loaders import ConfluenceLoader
from langchain_community.document_loaders.confluence import ContentFormat

# pip install markdownify  ← обязательная зависимость для keep_markdown_format

loader = ConfluenceLoader(
    url="https://confluence.yoursite.com/",
    token="<your-token>",
    space_key="MYSPACE",
    content_format=ContentFormat.VIEW,  # рендеренный HTML вместо сырого XML
    keep_markdown_format=True,          # конвертировать HTML → Markdown через markdownify
    include_attachments=True,
    limit=50,
    max_pages=1000,
)
documents = loader.load()
# Теперь таблицы → | col | col |, блоки кода → ```code```, даты и имена — обычный текст

Комбинация ContentFormat.VIEW + keep_markdown_format=True даёт двойной эффект: макросы раскрыты через VIEW, а Markdown-разметка помогает сплиттеру резать текст по смысловым границам (заголовки #, списки -, абзацы \n\n) вместо произвольных позиций.

Для сплиттера перешли на подсчёт размера чанка в токенах, а не в символах, и добавили перекрытие:

from langchain_text_splitters import RecursiveCharacterTextSplitter
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained("intfloat/multilingual-e5-large")

def token_length(text: str) -> int:
    return len(tokenizer.encode(text))

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=400,             
    chunk_overlap=50,           
    length_function=token_length, # считаем в токенах, а не символах
    is_separator_regex=False,
)
chunks = text_splitter.create_documents([doc.page_content for doc in documents])

Ключевой момент: length_function=token_length — без него chunk_size считается в символах, и реальный размер чанка в токенах непредсказуем. Это тихая ошибка, которую легко не заметить при первых тестах.

Retrieval

Индексация

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

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

Самый популярный алгоритм индексации для векторного поиска — HNSW (Hierarchical Navigable Small World). Именно его использует Qdrant по умолчанию.

Как работает HNSW

Скрытый текст

Алгоритм строит многоуровневый граф над всеми векторами. Каждый вектор — это узел графа, рёбра соединяют соседей. Уровней несколько: чем выше уровень, тем меньше узлов и длиннее «прыжки» между ними.

  1. Для каждого нового вектора случайным образом выбирается максимальный уровень — большинство точек попадает только на нижние уровни (распределение экспоненциальное).

  2. Начинаем с верхнего уровня с «точки входа» (entry point) и идём вниз, на каждом уровне находя локальный минимум расстояния.

  3. На базовом уровне (layer 0) ищем M ближайших кандидатов; параметр efConstruction задаёт размер очереди кандидатов при построении — больше значение, точнее индекс, но дольше строится.

  4. Новый вектор соединяется с найденными M кандидатами. Если у соседей число связей превышает M_max, лишние (самые дальние) отбрасываются.

Результат: поиск ближайшего соседа выполняется за O(log N) вместо O(N) при полном переборе. Главное преимущество HNSW в том, что он позволяет систематически обновлять индекс без полной переиндексации всей коллекции.

Векторная база данных

На этапе MVP мы рассматривали два варианта хранилища:

Векторная БД

Когда подходит

Qdrant

Большие коллекции, production-нагрузка, гибридный поиск, встроенный HNSW

Faiss

Быстрые эксперименты, тестирование гипотез, оптимизация под GPU

Для первого MVP выбрали Faiss — он позволял быстро проверять гипотезы без поднятия отдельного сервиса. Но уже на следующем этапе перешли на Qdrant: встроенный HNSW, поддержка гибридного поиска (dense + sparse), горизонтальное масштабирование и удобный REST/gRPC API для production-среды.

Generation

GPU для open-source LLM

Поскольку «Честный помощник» работает с закрытыми корпоративными данными, отправлять их в облачные API — OpenAI, Anthropic, Google — невозможно по соображениям безопасности. Только open-source модели, только on-premise.

И здесь большинство команд сталкиваются со стеной.

Видеокарты — это не деталь реализации, это условие существования всего проекта. Без достаточных GPU-мощностей вы физически не можете запустить современную LLM и получить приемлемое качество ответов в реальном времени. Большинство RAG-проектов, с которыми я знаком, разбились именно об этот камень: красивая архитектура на бумаге, но модель либо не влезает в память, либо отвечает слишком медленно, либо галлюционирует из-за своих мелких размеров.

Важно понимать: конечное качество системы определяется не тем, насколько тонко вы настроили индексацию и retrieval. Всё это не имеет значения, если ваша LLM не способна корректно следовать инструкциям в промпте и извлекать нужную информацию из переданного контекста. Размер модели — это не только объём знаний, но и способность «слушаться». Модели на 7B и на 70B параметров ведут себя принципиально по-разному при работе со сложными инструкциями.

Поэтому ещё до написания первой строки кода MVP — считайте ресурсы. Какую модель вы хотите запустить? Сколько VRAM она требует? Какую latency ожидает пользователь? Ответы на эти вопросы должны быть на столе до старта разработки, а не после первого деплоя.

В нашей компании мы прошли путь от попытки запускать модели на CPU до продовой развертки на GPU, результат сейчас кажется очевидным - без GPU вы не сможете получить приемлемое качество ответов в реальном времени. Но стоит понимать, что к моменту старта проекта на рынке еще не было такого количество open-source решений, которые позволяли бы однозначно подтвердить качество работы системы в целом.

Инференс для open-source LLM

Будем периодически возвращаться к этому пункту — он один из ключевых в нашей дальнейшей эволюции системы.

Когда GPU есть, возникает следующий вопрос: как именно запускать модель? Просто загрузить веса через transformers и вызвать model.generate() — можно, но в production это не работает: один запрос блокирует все остальные, нет batching’а, нет управления памятью. Нужен инференс-движок, который позволит держать продовую нагурзку и оптимизировать использование памяти.

Что такое KV-кэш?

Скрытый текст

При генерации текста трансформер на каждом шаге вычисляет матрицы Key и Value для всех токенов в контексте — это и есть механизм внимания (attention). Пересчитывать их заново для уже обработанных токенов на каждом следующем шаге бессмысленно: результат не изменится. Поэтому эти матрицы сохраняются в памяти GPU — в KV-кэше. Чем длиннее контекст, тем больше места занимает кэш: для модели 7B при контексте 8K токенов это может быть несколько гигабайт VRAM. Именно борьба за эффективное использование этой памяти и отличает production-движки от наивного запуска модели. Это позволяет: - обслуживать несколько запросов одновременно (continuous batching) без простоя GPU между ними, - эффективно переиспользовать кэш для повторяющихся префиксов промптов, - масштабироваться горизонтально на несколько GPU через tensor parallelism.

Выделю три варианта, каждый из которых предназначен (по моему мнению) для своей задачи.

Ollama — идеальный вариант для начальных стадией развития проекта.

Когда нужно быстро поднять модель локально и проверить гипотезу, Ollama — лучший выбор. Один командой устанавливается, одной командой запускается, есть REST API. Никакой конфигурации, никакого управления зависимостями.

Но у простоты есть цена. По умолчанию Ollama обрабатывает запросы последовательно (OLLAMA_NUM_PARALLEL=1) — параллелизм настраивается через переменные окружения, но даже с настройкой эффективность ниже, чем у специализированных движков: каждый параллельный слот требует отдельного KV-кэша и «съедает» дополнительные 15–25% VRAM. Для одного разработчика или небольшой команды — вполне приемлемо. Для сервиса с десятками одновременных пользователей — начинаются проблемы с latency и утилизацией GPU.

vLLM — production

vLLM решает главную проблему inference под нагрузкой — эффективное использование GPU-памяти. Ключевая идея — алгоритм PagedAttention: он управляет KV-кешем так же, как операционная система управляет страницами оперативной памяти.

На практике vLLM даёт в 2–4 раза больший throughput по сравнению с наивным запуском той же модели. Именно поэтому он стал нашим production-стеком.

SGLang — смотрим в будущее

SGLang — более молодой движок, оптимизированный под другой профиль нагрузки: короткие запросы с низкой latency. Там, где vLLM оптимизирует throughput (пропускную способность), SGLang оптимизирует время первого токена. Для агентных пайплайнов, где агент делает десятки коротких вызовов к модели последовательно, это критично.

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

Движок

Когда использовать

Ключевое преимущество

Ollama

Локальная разработка, быстрые эксперименты

Простота запуска

vLLM

Production, высокая нагрузка, много параллельных запросов

Throughput, PagedAttention

SGLang

Агентные пайплайны, streaming, низкая latency на коротких запросах

Время первого токена

API работы с LLM Для начинающих раработчиков

Поскольку наша команда работает только с open-source моделями, для нас с самого начала был важен практический вопрос: как именно взаимодействовать с LLM из продуктового бэкенда? Нужно было понять, как передавать запросы в модель, как получать ответы и как встроить это в существующую серверную архитектуру.

Если вы раньше не работали ни с API closed-source провайдеров, ни с локально развёрнутыми LLM, вопрос возникает почти сразу: допустим, модель мы подняли на своих GPU. Что дальше? Как написать слой, через который приложение будет с ней общаться?

К счастью, разработчики open-source inference-движков уже решили эту задачу. Такие системы, как vLLM, позволяют поднять OpenAI-compatible API, то есть HTTP-бэкенд, совместимый с OpenAI SDK. На практике это означает, что модель можно запустить локально командой вроде vllm serve <model_name> при работе с vLLM или ollama serve <model_name> при работе с Ollama, а дальше обращаться к ней почти так же, как к облачному API.

Это критически важно для разработки решений на базе open-source моделей, потому что такой API закрывает почти все базовые сценарии работы с LLM:

  • completion

  • structured output

  • tool calling

  • streaming

  • embeddings

Квантизация для open-source LLM

Еще на ранних этапах проекта мы поняли ещё одну важную вещь: квантизация — идеальный вариант для production. Без неё многие сильные open-source модели просто не помещаются в доступную VRAM, а стоимость инференса становится слишком высокой для реального продукта.

Квантизация — это перевод весов и иногда активаций модели в формат меньшей разрядности, чтобы сократить потребление памяти и ускорить инференс при минимальной потере качества. На практике чаще всего используют INT8, INT4 и FP8.

Например, подход GPTQ позволяет сжать модель до 4 бит с очень умеренной потерей качества. Для моделей масштаба OPT-175B и BLOOM-176B в статье про GPTQ показано, что:

  • квантование можно выполнить примерно за 4 GPU-часа на A100 80 GB,

  • при 4-битной квантизации потеря перплексии составляет не более 0.25 по сравнению с FP16,

  • кастомные ядра для умножения INT-матрица × FP-вектор дают ускорение до 3.25x на A100 и до 4.5x на A6000

Но здесь есть важная оговорка. Квантизация действительно хорошо подходит для production, если вы заранее проверили модель на собственных доменных сценариях. Снижение качества после квантизации может быть почти незаметным на общих бенчмарках и при этом болезненно проявляться именно в ваших задачах: хуже соблюдаются инструкции, теряются редкие термины, ломается structured output или падает качество работы с длинным контекстом.

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

GPTQ

GPTQ

Итог MVP

К моменту завершения первой версии архитектура выглядела так: два самописных бэкенда, Qdrant в качестве векторного хранилища, эмбеддер ai-sage/Giga-Embeddings-instruct для векторизации и Qwen2.5-72B-Instruct-GPTQ-Int8 на vLLM в качестве основной языковой модели. Ранжирование результатов поиска — через bi-encoder.

Система работала и отвечала на вопросы. Но у неё был один принципиальный изъян: на основе отобранных чанков топ-10 чанковых мы передавали в LLM полные тексты статей из Confluence откуда эти чанки были взяты, размером до 120 000 токенов. Логика была понятна — не хотелось ничего терять при разбивке на чанки. Однако на практике это приводило к трём проблемам:

  • высокая стоимость каждого запроса к модели,

  • заметная latency — пользователь ждал дольше, чем хотелось бы,

  • качество ответов не всегда оправдывало ожидания: при очень длинном контексте модель хуже фокусируется на нужном фрагменте.

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

MVP дал нам главное — работающий прототип и понимание узких мест. Стало ясно, что следующий шаг — не увеличивать контекст, а научиться передавать в модель только то, что действительно нужно для ответа. Так начался следующий этап — Advanced RAG.

Что дальше

Мы разобрали терминологию и прошли полный цикл создания MVP: от индексации документов до генерации ответов в Naive RAG. Это рабочий фундамент, но его потолок мы почувствовали быстро — качество поиска и генерации упиралось в ограничения подхода. Во второй части статьи покажу, как мы этот потолок пробивали:

  • Advanced RAG — реранжирование, гибридный поиск, продвинутый чанкинг и всё, что превращает наивный поиск в production-grade решение;

  • Modular RAG — переход от монолитного пайплайна к мультиагентной архитектуре, две итерации (детерминированный конвейер → ReAct-агент);

  • Оценка качества через RAGAS — как мы построили бенчмарк, сгенерировали тестовый датасет и измерили разницу между подходами (+7 п.п. к Overall);

Автор статьи: Константинов Александр - Старший AI-инженер, Лаборатории искусственного интеллекта «Честного знака»