Салют, Хабр!

Я Антон, продакт сервисов клиентской документации. Одна из задач нашей команды — решать проблемы пользователя как можно проще: без долгих диалогов с чат-ботом или техподдержкой, где приходится излагать, что случилось, как и когда; без поисковиков в надежде, что где-то на форумах найдётся ответ. Поэтому мы запустили на интеллектуальных телевизорах и медиацентрах Сбер сервис персонализированной контекстной помощи — ГигаСправку. Теперь, если устройство выдаёт ошибку, вместе с ней на экране появляется кнопка ГигаСправки. Нажав на неё, пользователь получает совет, как можно исправить проблему с учётом конкретного устройства и его состояния.

Эффект от нового сервиса для техподдержки огромен, а техническая реализация проста: всего одна точка знаний, RAG-сервис, ГигаЧат и семантическое кэширование позволяют предельно быстро помогать юзеру на той же поверхности. Показывать то, что нужно, там, где нужно. Рассказываем, как превратили базу с ответами на вопросы в ГигаСправку.

Ошибки, которые мы решаем

Изначально база знаний существовала только на сайте sberdevices.ru в разделе техподдержки. Пользователь попадал туда, когда вбивал в поисковике свою проблему. Выходило, что проблема и решение жили на разных поверхностях, плюс даже в нужной статье человеку было нужно определить релевантный именно для него кейс. 

ГигаСправка позволила предельно улучшить этот User Experience. Мы практикуем подход Quality Gates, поэтому изначально сделали MLP на группе из трёх ошибок. Выбрали те, на которых проще всего было получить обратную связь и оценить эффект от сервиса:

1. проблемы с просмотром телеканалов — слабый сигнал, канал зашифрован…

2. Нет сигнала на одном из разъёмов, например, на HDMI.

3. Проблемы при выполнении заданий во внутреннем сервисе геймификации Планетариум.

Первые две ошибки чаще связаны с проблемами на стороне пользователя или сервиса. Третья обычно требует не исправления со стороны разработчиков, а подробного пояснения, как именно выполнять задание, для владельца телевизора или медиацентра.

Пример работы ГигаСпаравки: пользователь меняет источник сигнала на внешний видеовход и получает ошибку: «Нет HDMI-сигнала». Одновременно с ошибкой появляется кнопка: «Почему такая ошибка?». Нажав её, он запускает сбор контекста — информации об устройстве, нужной для справки: какой конкретно из HDMI-разъёмов он выбрал как источник сигнала? Подключены ли устройства к другим разъёмам? Включена ли функция HMDI CEC? Какая на ТВ версия прошивки?

RAG-сервис разыскивает в базе релевантные чанки статей. ГигаЧат с опорой на контекст формулирует ответ и отображает на экране. Готово!  

Cовет по ошибке c HDMI в интерфейсе
Cовет по ошибке c HDMI в интерфейсе

В персонализированную справку приоритетно имеет смысл выносить проблемы, которые человек может решить сам и немедленно. Её можно дополнительно использовать, чтобы проговорить пользователю, если временный сбой — на стороне устройства. К примеру, при ошибке в сервисе Планетариум ГигаСправка в первом же абзаце сообщает, что технических проблем нет. А если они есть, просто добавляем это в соответствующую статью базы знаний. Тогда справка в интерфейсе оповещает: проблема известна, решаем.

А теперь давайте провалимся в сервис поглубже.

Из базы в чанки

База клиентской документации — это все знания о сервисе, которые есть. В нашем случае — знания об устройствах, сочетаниях устройств, их настройках, сервисах, возможностях и, разумеется, потенциальных ошибках. База лежит в разделе /help на сайте sberdevices.ru в соответствующем разделе, пополняется и актуализируется ежедневно, а иногда — несколько раз в день. 

Когда мы пополняем базу, одновременно с нею обновляется и векторная база данных, которую использует наш RAG-сервис DocSense. У нас (как и у всех) настроен DevOps пайплайн доставки изменений, и все шаги интегрированы в него:

Шаг 1. Страница → Документ

После создания статьи для сайта джоба автоматически конвертирует её в Markdown и обогащает метаданными (URL, заголовки, положение в дереве). Как показала практика, иерархия позволяет различать схожие статьи, а ссылку ГигаЧат использует в ответе. Мы загружаем данные постранично. Одна страница в Markdown — это одна единица контента; так проще отслеживать, какая статья обновилась. На выходе получаем массив UploadDataItem[]:

export interface UploadDataItem {
pageContent: string; // Содержимое страницы в Markdown
metadata: {
source: { url: string };
heading: { depth: number; text: string };
// ... другие метаданные
};
}

Шаг 2. Отправка в uploader

С помощью нашего CI/CD пайплайна отправляем все страницы разом в uploader.

Шаг 3. Чанкинг каждой страницы

Uploader берет каждую страницу отдельно и режет её на чанки с помощью RecursiveCharacterTextSplitter. Каждый полученный чанк векторизуется моделью EmbeddingsGigaR. Параметры чанкинга — это:

  • chunkSize: 4000 символов — примерно 1366 токенов.

  • chunkOverlap: 400 символов — перекрытие, чтобы важный контекст не терялся на границах.

  • separators — настраиваемые разделители (по умолчанию: ["\n\n", "\n", " "]).

Шаг 4. Загрузка в Qdrant с UPSERT

Готовые точки загружаются в Qdrant с использованием UPSERT. Благодаря UPSERT, если статья обновилась, старые чанки заменяются новыми (по одинаковому id, сгенерированному из содержимого — не нужно удалять всю коллекцию при обновлении одной статьи). Идемпотентность: повторная загрузка тех же данных не создает дубли.

Умный поиск (RAG-сервис DocSense) на базе ГигаЧата был изначально разработан и используется в developers.sber.ru/docs, а теперь его применение расширено до ТВ-поверхностей.  

Для observatility используется Arize Phoenix. В нём есть важная для нас функция — трассировка шагов. При ней смотрим, какие чанки были извлечены из Qdrant и с каким score в результате поиска по векторам, на что в ответе опирался ГигаЧат.

Пример чанка, который вернул retrieval-поиск
Пример чанка, который вернул retrieval-поиск

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

События вызова ГигаСправки, сгенерированные ответы и оценки ответов (о них ниже) собираются в аналитической СУБД для дашбордов и отчётов, которые позволяют оценивать качество работы сервиса.

Пайплайн в RAG-сервисе выглядит так:

Необходимость переписывать запрос связана с тем, что поиск в векторной базе работает на основе косинусного сходства между вектором запроса и векторами чанков в базе. Пользователи формулируют вопросы по-разговорному: «А как мне настроить звук на телевизоре?», «С какой стати у меня не работает HDMI?». Лишние слова — «А как мне?», «с какой стати» — занимают место в векторе, но не несут смысловой нагрузки. В результате: 

  • Вектор запроса «размазывается» по шумовым словам.

  • Снижается косинусное сходство с релевантными чанками.

  • Модель может найти не тот чанк или ничего не найти.

Очистить запрос от «воды» помогает LLM-препроцессор:

export const CLEAN_QUERY_PROMPT = `Ты — препроцессор поисковых запросов. Очисти запрос пользователя от разговорных оборотов и выдай поисковую формулировку.
Правила:
1. Убери слова и фразы: "найди", "расскажи", "покажи", "подскажи", "пожалуйста" и похожие.
2. Оставь только сущности, предметную область.
3. Не добавляй новые слова, которых нет в запросе.
4. Если вопрос составной, раздели его на под-вопросы.


Ответь строго JSON:
{"cleanQuery": "...", "subQueries": ["..."]}`;

Так разговорный запрос «подскажи, что делать, если нет сигнала» превращается в поисковый — «Нет сигнала». Переписывание запроса повышает косинусное сходство — релевантные чанки получают более высокий score и уменьшает false positive: шумные запросы больше не матчатся случайно с нерелевантными чанками. Вдобавок это позволяет экономить токены: запрос короче — токенов для эмбеддинга нужно меньше.

Говорит сервис

Для персонализации ответа необходим контекст пользователя. Он собирается на двух уровнях сразу. Первый — системный: модель устройства, версия прошивки, используемые пользователем разъёмы ТВ и так далее. Конкретный запрашиваемый список зависит от ошибки. Второй — прикладной слой приложения, которое вызвало контекстную справку, например, что смотрел пользователь — эфирное ТВ, спутниковое, кабельное? И какой канал? 

Двухуровневый контекст передаётся в качестве входного параметра в команду на открытие ГигаСправки. Она подключается к приложениям как SDK; сбор контекста происходит в момент запуска её экрана. По сути за оба слоя отвечает само приложение, которое вызывает контекстную справку.  

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

Трейс запроса из Phoenix
Трейс запроса из Phoenix

Системный промпт — это роль, которую берёт на себя ГигаЧат: ИИ-ассистент по поддержке, который отвечает на вопросы о телевизорах Сбер и телевизорах на платформе Салют ТВ. Доступные роли помощника заранее заданы в конфигурации сервиса. Здесь же описана его манера общения и указано обращение к пользователю. 

Согласно промпту в ответе Гигачата первыми должны быть рекомендации, которые проще всего выполнить, а потом уже более сложные варианты. Указываем, что необходимо учитывать контекст.

С опорой на промпт, а точнее, переменные в промпте, RAG-сервис ищет по базе куски подходящих знаний и отдаёт ГигаЧату. На их основе он генерирует ответ пользователю. 

Кэш

Чтобы сократить время ожидания пользователя, а заодно сэкономить токены, в сервисе используется семантическое кэширование. Если классический кэш ищет точное совпадение текста, то семантический — смысловую близость. Поиск в векторизированной базе производится по близким векторам предыдущих вопросов. Чем более типизированы ошибки и чем меньше вариантов контекста пользователя, тем больше запросов RAG-сервис способен покрыть кэшем. Даже если настроить параметр схожести максимально строго — так, чтобы одно слово разницы во входящем пользовательском промпте запрещало использовать это как кэш. По нашим подсчётам без кэша сервис потреблял бы на два порядка больше токенов ГигаЧата. 

Хранится кэш в отдельной коллекции в БД Qdrant. Это быстрое и эффективное решение, которое особенно хорошо подходит для RAG-систем.

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

Метрики качества

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

Кроме того, сейчас мы тестируем подход, где RAG-сервис анализирует поступившие ответы с помощью концепции LLM-as-Judge, оценивая точность и качество ответа, релевантность вопроса, изменения качества базы знаний. Эти метрики позволяют в в динамике наблюдать, насколько хорошо система справляется с ответами на заданные вопросы. Пока оценка проводится в ручном режиме, но в будущем можно будет автоматизировать её и связать с кэшем: если закэшированный ответ плохой, будем удалять его и больше не использовать. 

Заключение

Уже на этапе MLP мы убедились, что сервис нужен: владельцы интеллектуальных ТВ запускают ГигаСправку несколько тысяч раз в день. CSAT ответов составляет 70% при том, что мы реализовали далеко не все идеи. Раз он востребован, можно расширять количество ошибок, которые он покрывает, и совершенствовать его. Например, в ближайшее время у пользователя появится возможность продолжить диалог, который начат на интеллектуальном телевизоре, в вебе; это полезно в сценариях, когда нужно перезагрузить устройство или интернет на нём. Пользователь хочет решения проблем, которые релевантны конкретно для него, и чтобы они были представлены в максимально удобном виде. Персонализированная контекстная справка — это способ максимально быстро дать человеку именно такой ответ.

Комментарии (0)