Відділ контролінгу запитує асистента: „Який був чистий дохід філії у Гданську в Q3 2024?" Модель повертає число. Проблема: ніхто не знає, з якого аркуша воно походить, чи це версія після корекції, чи драфт, і чи модель прочитала його правильно, чи округлила на основі подібних рядків. Структуровані дані в RAG — це окрема категорія проблем, значно складніша за retrieval за текстовими документами.
Чому стандартний чанкінг руйнує таблиці
Стандартний текстовий спліттер бачить таблицю як безперервний потік символів і ділить її за кількістю токенів. Якщо таблиця має 40 рядків, спліттер розріже її приблизно навпіл. Фрагмент A отримає заголовки стовпців і рядки 1-20. Фрагмент B отримає рядки 21-40 без заголовків.
Наслідки серйозні. Модель retrieval-ує фрагмент B і не знає, що третій стовпець — це „Чистий дохід [PLN]", а не „Маржа [%]". Генерація відповіді на основі такого фрагмента — це, по суті, вгадування. При запитаннях про конкретне значення відсоткова похибка тут часто становить 100%, бо модель бере число з неправильного стовпця.
Ця проблема стосується електронних таблиць (XLSX, CSV), експортів із систем ERP і CRM, фінансових звітів у табличному форматі та результатів SQL-запитів, збережених як файли. У кожному випадку семантична цілісність рядка залежить від заголовка, який стандартний спліттер відрізає.
Два режими retrieval для структурованих даних
#Немає єдиного підходу, який працює для всіх запитань про табличні дані. У Cashcrown застосовуємо два режими й обираємо між ними на етапі класифікації запиту.
Text-to-SQL (або text-to-query) підходить, коли запитання агрегаційне або точкове: „Який був сукупний дохід у Q3?", „Покажи 5 клієнтів з найвищою заборгованістю", „Скільки замовлень мали вартість понад 50 тис. zł?". LLM перекладає природне запитання на SQL-запит, який виконується на реальній базі або на схемі, завантаженій у пам'ять. Результат детермінований і має однозначний провенанс: конкретна таблиця, конкретні стовпці, конкретна умова WHERE.
Обмеження text-to-SQL реальні: модель повинна знати схему, запитання про дані, розкидані по багатьох таблицях, потребують складних JOIN-ів, а некоректно згенерований запит поверне хибне число без попередження. Кожен згенерований запит має перевірити людина перед першим запуском на продуктивних даних, а система повинна логувати кожен виконаний query з timestamp і користувачем.
Семантичний retrieval по денормалізованих рядках підходить для контекстових і патернових запитань: „Який клієнт має профіль, подібний до компанії X?", „Знайди продукти, які мали схожі цикли продажів", „Які категорії витрат зростали непропорційно в останньому році?". Тут йдеться не про одну цифру, а про зв'язки, аномалії та подібності, які не можна виразити простим SQL-запитом.
Schema-aware чанкінг: як зберегти заголовки
#Незалежно від обраного режиму, етап підготовки даних критичний. Для табличних даних застосовуємо schema-aware підхід, який відрізняється від стандартного чанкінгу, описаного детальніше в статті про чанкінг документів для RAG.
Принцип простий: кожен рядок таблиці, який потрапляє до векторного індексу, повинен містити заголовки стовпців. Не в окремому фрагменті, а інлайн, в тому ж чанку. Формат Markdown добре підходить, бо він зрозумілий для моделі й зберігає зв'язок стовпець-значення:
Філія: Гданськ | Період: Q3 2024 | Чистий дохід [PLN]: 1 247 890 | Маржа брутто [%]: 34,2 | Версія: після корекції 2024-10-15
Кожен такий рядок-чанк отримує метадані: назва вихідного файлу, аркуш або назва таблиці, номер рядка в оригінальному файлі, дата версії документа, тип даних (фінансові, продажі, кадри). Метадані дозволяють фільтрувати перед векторним пошуком, що детально описано в матеріалі про гібридний пошук BM25 і векторів.
Для великих таблиць (понад 500 рядків) індексуємо не все. Індексуємо рядки, які мають потенціал відповіді на бізнес-запитання, тобто рядки з ненульовими значеннями, флагами винятків, агрегаціями на рівні відділу або періоду. Гранулярні рядки потрапляють до SQL-бази, доступної через text-to-SQL.
Гібридний підхід: коли поєднувати обидва режими
| Тип запитання | Рекомендований режим | Приклад | Валідація людиною |
|---|---|---|---|
| Конкретне значення з однієї комірки | Text-to-SQL | „Дохід філії X у березні" | Query на рев'ю перед продакшеном |
| Умовна агрегація | Text-to-SQL | „Сума замовлень понад 10 тис. у Q2" | Query на рев'ю + результат на перевірку |
| Подібність, патерн | Семантичний retrieval | „Клієнти, подібні до сегмента A" | Top-k результатів на оцінку |
| Аномалія, відхилення | Семантичний retrieval + SQL | „Які витрати зростали швидше за доходи?" | Обидва кроки на перевірку |
| Змішане запитання | Гібридний (retrieval + SQL join) | „Підсумуй Q3 і вкажи подібні історичні періоди" | Повний шлях на аудит |
Змішані запитання найскладніші. Асистент повинен спочатку отримати конкретні цифри через SQL, а потім збагатити їх семантичним контекстом із подібних періодів або продуктів. Така архітектура вимагає оркестратора, який склеїть обидва результати перед генерацією. Human-gate на такому складному результаті обов'язковий, якщо відповідь живить управлінське рішення.
Провенанс цифри: цитуй, не наближуй
Це жорстка вимога архітектури, яку в Cashcrown виконуємо на рівні системного промпту й перевіряємо в golden set оцінки.
Кожна цифра, повернута асистентом, повинна мати атрибуцію до конкретного джерела. Формат провенансу залежить від режиму:
Для text-to-SQL: „1 247 890 PLN (джерело: raport_wyniki_Q3_2024.xlsx, аркуш Філії, рядок 47, версія після корекції 2024-10-15, запит: SELECT przychod_netto FROM oddzialy WHERE nazwa='Gdańsk' AND okres='Q3_2024')".
Для семантичного retrieval: „34,2% (джерело: фрагмент рядка 47 з raport_wyniki_Q3_2024.xlsx, косинусна відстань 0,97)".
Модель ніколи не повинна округлювати або інтерполювати цифру, яку не знайшла в індексі. Якщо дані недоступні, відповідь звучить: „Цієї цифри немає в базі. Перевірте файл X або попросіть аналітика про експорт." Це краще, ніж цифра, яка виглядає правдоподібно, але походить з найближчого подібного рядка.
Детальніше про те, як будувати системи AI, що відповідають за корпоративні дані, у статті про корпоративний GPT на базі знань.
Спробуй: RAG над фінансовими даними у твоїй компанії
#Фільтрація та безпека структурованих даних
Фінансові, кадрові та комерційні дані рідко повинні бути доступні всім користувачам асистента. В архітектурі RAG над структурованими даними фільтрацію доступу реалізують через метадані чанка: кожен рядок має мітку access_level (наприклад, „керівництво", „контролінг", „продажі") та tenant_id у багатокомпанійних системах.
Векторний запит фільтрує за цими метаданими перед переходом до семантичного ранжування. Завдяки цьому контролер не бачить кадрових даних, а продавець — маржі нетто. Деталі реалізації ізоляції доступу описані в статті про векторну базу та критерії вибору.
Другий вимір безпеки — свіжість даних. Фінансові аркуші версіонуються. Векторний індекс, побудований на драфті від 12 жовтня, повертатиме інші цифри, ніж той, що побудований на версії після корекції від 15 жовтня. Система повинна зберігати версію документа як метадані кожного чанка й показувати її користувачеві при кожній відповіді. Рішення про те, яку версію вважати чинною, належить людині, а не асистенту.
Для регуляторних звітів (наприклад, фінансова звітність, звіти до KNF) рекомендуємо підхід лише для читання з логуванням кожного запиту та відповіді. Вимоги AI Act щодо документування систем високого ризику можуть поширюватися на подібні асистенти, якщо вони впливають на фінансові рішення. Детальніше про аналіз даних і BI у статті AI для аналізу даних і BI.
FAQ
#Чи безпечний text-to-SQL на продуктивних даних?
#Text-to-SQL повинен працювати на копії лише для читання (read replica) або на схемах з правами SELECT виключно для облікового запису асистента. Кожен згенерований запит варто логувати з timestamp і ідентифікатором сесії. Перед впровадженням у продакшен рекомендуємо перегляд щонайменше 50 прикладів запитів аналітиком або розробником, який знає схему. Некоректно згенерований JOIN або помилкова умова WHERE може повернути цифру, що виглядає правдоподібно, але розрахована на хибному підмножині.
Що робити з таблицями, стовпці яких мають неоднозначні назви?
Етап підготовки даних повинен включати мапінг технічних назв стовпців на бізнес-описи. Стовпець rev_net_adj_eur у схемі стає „Чистий дохід після корекції [EUR]" у метаданих чанка та схемі text-to-SQL. Це аналітична робота, яку має виконати людина, що знає домен; LLM може запропонувати мапінг, але остаточне рішення щодо бізнес-назви належить відділу, який цими даними користується.
Як обробити таблиці зі злитими комірками (merge cells) у XLSX?
#Об'єднані комірки — одна з найпоширеніших проблем при парсингу XLSX. Бібліотеки на кшталт openpyxl повертають значення лише для лівої верхньої комірки об'єднаного діапазону, решта порожні. На етапі парсингу потрібно розповсюдити значення об'єднаної комірки на всі рядки, які вона охоплює, перш ніж дані потраплять до чанкінгу. Без цього розповсюдження модель бачить рядки без заголовка групи, що руйнує контекст і призводить до хибних атрибуцій.
Як оцінювати якість RAG над табличними даними?
#Golden set для табличних даних повинен містити запитання з точними, верифікованими відповідями: конкретна цифра з конкретної комірки. Метрика оцінки — це не лише семантична релевантність, а й числова точність: чи збігається повернуте значення з джерелом до останньої копійки (для фінансових сум толерантність має бути нульовою). Для RAG над змішаними даними варто будувати окремі golden set для режиму SQL і семантичного режиму, оскільки вони відрізняються метрикою успіху.
Чи покращує гібридний пошук точність для числових даних?
У обмеженій мірі. BM25 добре справляється з точним текстовим збігом, наприклад, назв філій, кодів продуктів, номерів договорів. Для самих чисел сигнал BM25 слабкий, бо ті самі цифри зустрічаються в багатьох рядках. Гібридний пошук допомагає при змішаних запитаннях, де користувач вказує назву й запитує про число. Для суто агрегаційних запитань text-to-SQL надійніший за будь-яку форму векторного retrieval.