Перейти к содержанию
Публикация AiManual

Text2SQL в ЮMoney: как устроен агентный сервис для аналитических данных

Внутренний Text2SQL-сервис ЮMoney принимает вопрос на русском и сам пишет SQL: разбираем пайплайн на LangGraph, циклы графа, два уровня памяти диалога и форматы

Коротко

Что будет в материале

  1. 01

    Что такое Text2SQL-сервис ЮMoney и какие задачи он решает

  2. 02

    Почему LangGraph, а не свободный агент: архитектура пайплайна

  3. 03

    Четырёхслойная валидация SQL: как отлавливаются семантические ошибки

  4. 04

    Как результат превращается в понятный ответ: текст, таблица, Excel или график

Text2SQL-сервис ЮMoney принимает вопрос на русском языке, сам находит нужные таблицы, пишет SQL-запрос, проверяет его, выполняет и возвращает ответ в виде текста, таблицы, Excel-файла или графика. Проект вырос из внутреннего хакатона: начинался как эксперимент внутри компании, а не как продукт с готовым техническим заданием. Разбор сервиса опубликован на Хабре старшим аналитиком данных ЮMoney.

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

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

Что такое Text2SQL-сервис ЮMoney и какие задачи он решает

Задача сервиса узкая и прикладная: убрать из процесса получения цифры шаг, на котором аналитик вручную пишет запрос. Пользователь формулирует вопрос обычным языком, а дальше система сама ищет данные, генерирует SQL, проверяет его и выполняет. Ответ приходит в одной из четырёх форм: текст, таблица, Excel-файл или график.

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

Диалоговый режим отличает такой сервис от поисковой строки. Уточнение «А в декабре?» не требует повторять весь вопрос заново: система помнит предыдущий шаг и достраивает новый запрос от него. В начале работы можно выбрать режим B2B, B2C или специфичный сценарий, тогда поиск идёт только по темам соответствующего направления. По умолчанию включён автоматический режим.

Почему LangGraph, а не свободный агент: архитектура пайплайна

LangGraph выбран ради предсказуемости. Свободный агент сам решает, какой инструмент вызвать следующим, и на длинной цепочке шагов его траектория становится плохо объяснимой: чем больше свободы, тем сложнее сказать, почему на выходе получился именно такой запрос. Граф ограничивает пространство решений: набор узлов и переходов фиксирован, а LLM работает внутри конкретного шага. Автор формулирует мотивацию коротко: LangGraph даёт предсказуемый и управляемый процесс вместо свободного агента.

Пайплайн делится на две части: подготовку контекста и генерацию SQL. Первая отвечает на вопрос «что именно нужно посмотреть», вторая - «как это выразить в SQL». Разделение удобно тем, что ошибки контекста и ошибки генерации разбираются отдельно: неправильно подобранная таблица и неправильный filter по дате требуют разных правок.

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

Спор о том, что важнее для качества - модель или обвязка вокруг неё, - не уникален для Text2SQL. Похожий вопрос разбирается в отдельном материале о 1С Напарник и open-source агенте на Deep Agents, PostgreSQL FTS и pgvector.

Ключевые узлы: от переформулировки вопроса до сборки контекста

Точное устройство каждого узла автор разбора не описывает, но состав состояния и логика графа показывают, какие шаги есть в подготовке контекста:

  • Переформулировка вопроса. Пользовательский вопрос переписывается с учётом истории диалога. Без этого шага уточнение «А в декабре?» осталось бы без объекта: непонятно, к какому показателю относятся новые условия.
  • Определение предметной области. Система решает, к какой теме относится вопрос. Если в начале диалога выбран режим B2B, B2C или специфичный сценарий, поиск ограничивается темами этого направления.
  • Нормализация дат. Формулировки вида «в мае» или «за последние недели» превращаются в конкретные границы периода, которые понимает SQL. Найденные даты попадают в состояние.
  • Выбор таблиц. Под вопрос подбираются таблицы и колонки. Этот шаг даёт цикл: если проверка сочла набор недостаточным, система вернётся и подберёт таблицы заново.
  • Сборка контекста. Из выбранных таблиц, описаний и истории собирается то, что уйдёт в генерацию SQL.

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

Два уровня памяти диалога: снимки и сжатое состояние

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

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

Работает это так. Пользователь спрашивает про кошельки, открытые в мае, получает ответ, затем пишет: «А в декабре? И сгруппируй по неделям». Снимок даёт системе тему и предыдущий SQL, сжатое состояние - общий контекст переписки, и новый запрос собирается уже с декабрьским периодом и группировкой по неделям.

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

Четырёхслойная валидация SQL: как отлавливаются семантические ошибки

Проверка SQL - критичный этап, потому что языковая модель уверенно выдаёт запросы, которые выполняются без ошибок и возвращают неверный результат. Синтаксически корректный SQL с фильтром не по тому периоду или join не по той колонке не вызовет исключения в базе: вы просто получите правдоподобное число, а расхождение заметите уже в отчёте.

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

  • Синтаксис и выполнимость. Запрос должен парситься и запускаться на реальной СУБД.
  • Схема. Таблицы и колонки из запроса существуют, типы сопоставимы, нет обращения к удалённым или переименованным полям.
  • Логика. Запрос отвечает именно на заданный вопрос: тот ли показатель, тот ли период, та ли группировка. Здесь и ловятся расхождения вида «спрашивали про май, а в WHERE стоит декабрь».
  • Ограничения и безопасность. Проверка на тяжёлые запросы, доступ к чувствительным полям и выход за разрешённую область данных.

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

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

Как результат превращается в понятный ответ: текст, таблица, Excel или график

После выполнения SQL система возвращает ответ в одной из форм: текст, таблица, Excel-файл или график. Формат подбирается под характер вопроса и объём данных.

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

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

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

Наблюдение и отладка: Langfuse и «след решений» для тихих сбоев

Самая неприятная категория ошибок в Text2SQL - тихие сбои. Запрос выполнился, ответ пришёл, ошибок в логах нет, только цифра неправильная. Обычное логирование здесь помогает слабо: нужно видеть не факт падения, а цепочку решений, которая привела к ответу.

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

В описании сервиса для наблюдения упоминается Langfuse - платформа трассировки LLM-приложений, которая собирает трейсы вызовов, промпты и оценки. Конкретики по настройке (какие метрики снимаются, как устроены дашборды, что считается алертом) в опубликованном фрагменте разбора нет, поэтому этот блок стоит воспринимать как направление работы, а не как готовый рецепт.

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

Контур экспериментов и тестирование с LLM-судьёй и ручной разметкой

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

Схема, заявленная в описании сервиса: LLM-судья оценивает, правильно ли сгенерирован SQL и ответ, а ручная разметка используется для калибровки и разбора сложных случаев. Контур экспериментов позволяет сравнивать версии пайплайна и промптов между собой на одном и том же наборе. Детали (размер набора, состав рубрик, метрики) в доступном фрагменте не раскрыты.

Как это устроено в других командах, хорошо видно на примере оценки агентов для длинных исследовательских отчётов: там LLM-судья работает по рубрикам, отдельно проверяется фактическая точность, а A/B-сравнение идёт с базовой версией. Разбор этого опыта есть в материале об оценке AI-агентов для длинных отчётов в кейсе Similarweb и уроках LangSmith. Отдельный материал про LangSmith в медицинском AI показывает, как из экспертизы делают датасеты, судей и автоматические релизные гейты.

Для Text2SQL главный урок из таких кейсов простой: судья должен проверять не «похоже ли на правду», а конкретные свойства запроса - период, показатель, гранулярность группировки. Иначе аккуратно выглядящая оценка замаскирует регресс, и вы узнаете о нём от пользователя, а не из отчёта.

Кому подходит такой подход и какие у него ограничения

Кейс полезен продакт-менеджерам, аналитикам и разработчикам, которые решают, стоит ли строить Text2SQL у себя. Он показывает рабочую композицию: граф вместо свободного агента, отдельная подготовка контекста, проверка SQL перед выполнением, память диалога и наблюдаемость. Ничего экзотического в стеке нет, сложность в стыковке частей.

Ограничения стоит взвесить до старта:

  • Качество описаний данных. Выбор таблиц опирается на то, как данные задокументированы. Устаревшие и неполные описания бьют по точности сильнее, чем замена модели.
  • Сложность проверок. Синтаксис и схему проверить дёшево, логику - дорого. Слой, который сверяет вопрос и запрос, требует отдельной работы и своих тестов.
  • Зависимость от LLM. Качество ответа ограничено возможностями модели на конкретных типах запросов. Многошаговые вопросы со сложными join остаются узким местом.
  • Тестовый набор. Ручная разметка и поддержка эталонных ответов - постоянные затраты, а не разовая задача перед запуском.
  • Интеграция. Сервис внутренний: ему нужны доступ к хранилищу, права пользователей и точки входа там, где аналитики и менеджеры уже работают.

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

Подписаться на канал