Как вы делаете retrieval для структурированных данных (SQL, Knowledge Graph)?
Краткий тезис
Retrieval для структурированных данных принципиально отличается от векторного поиска по тексту: вместо эмбеддингов и косинусной близости используется перевод естественно-языкового запроса в формальный язык запросов (SQL для реляционных баз, SPARQL/Cypher для графов знаний). Ключевая задача — точное понимание схемы данных и генерация корректного запроса, результаты которого затем подаются LLM как контекст. Такой подход позволяет отвечать на точные фактологические вопросы, агрегации и запросы по связям, недоступные при поиске по чанкам.
1. Термин: Структурированные данные в контексте RAG
Структурированные данные — это данные, организованные по строгой схеме: таблицы с колонками и типами (SQL) или графы с узлами, рёбрами и свойствами (graph|Knowledge Graph). В отличие от неструктурированного текста, они требуют точного соответствия между запросом и схемой. Ошибка в имени таблицы или синтаксисе запроса делает результат бесполезным.
Зачем нужен отдельный подход
- поиск|Векторный поиск по сериализованным таблицам (например, «превратить строку таблицы в текст») теряет структуру и связи.
- LLM не может напрямую «выполнить» SQL — нужен промежуточный шаг генерации запроса.
- Результаты структурированного retrieval — это не чанки, а строки, ячейки или пути графа, которые затем форматируются для LLM.
2. Text-to-SQL: общий пайплайн
Text-to-SQL — задача преобразования естественного языка в SQL-запрос. Пайплайн:
- Пользовательский запрос (NL): «Сколько заказов сделал клиент с ID 42 в марте 2024?»
- Формирование промпта: LLM получает схему БД (таблицы, колонки, типы, внешние ключи) + запрос + инструкцию.
- Генерация SQL: LLM выводит, например,
SELECT COUNT(*) FROM orders WHERE customer_id = 42 AND order_date BETWEEN '2024-03-01' AND '2024-03-31'. - Выполнение SQL на реальной БД (с проверкой безопасности).
- Форматирование результата: строки таблицы превращаются в текст или Markdown-таблицу.
- Передача контекста LLM для финального ответа.
Пример промпта (упрощённо):
Ты — SQL-генератор. Схема БД:
Таблица orders: id (INT), customer_id (INT), order_date (DATE), total (FLOAT)
Таблица customers: id (INT), name (VARCHAR)
Запрос пользователя: "Покажи общую сумму заказов для клиента 'Иван' за 2024 год."
Сгенерируй только SQL-запрос.
LLM ответит: SELECT SUM(o.total) FROM orders o JOIN customers c ON o.customer_id = c.id WHERE c.name = 'Иван' AND YEAR(o.order_date) = 2024.
3. Проблемы Text-to-SQL и их решения
| Проблема | Описание | Решение |
|---|---|---|
| Неверная генерация SQL | LLM путает имена таблиц/колонок, синтаксис | Few-shot примеры, fine-tuning на датасетах (Spider, WikiSQL), проверка через парсер |
| SQL-инъекции | Злонамеренный запрос в NL может сгенерировать опасный SQL | Использовать параметризованные запросы (не конкатенацию), ограничить права БД (только SELECT) |
| Сложные запросы | Много JOIN, подзапросов, оконных функций | Разбивать на подзадачи (агентный подход), использовать chain-of-thought |
| Ошибки выполнения | БД возвращает ошибку (несуществующая колонка, деление на ноль) | Повторная генерация с сообщением об ошибке в промпте |
| Неоднозначность | «клиенты из Москвы» — что значит «из Москвы»? (город в адресе или регион?) | Уточняющий диалог с пользователем или жёсткое правило в промпте |
Fine-tuning на специализированных датасетах (например, Spider, Bird) повышает точность генерации SQL. Self-consistency (генерация нескольких SQL, выполнение, выбор по большинству) снижает риск единичной ошибки.
4. Retrieval из Knowledge Graph (KG)
Knowledge Graph — это графовая структура, где узлы — сущности (люди, компании, фильмы), рёбра — отношения (работает_в, снялся_в), а свойства — атрибуты (возраст, дата). Для извлечения данных используются языки запросов:
- SPARQL — для RDF-графов (Wikidata, DBpedia).
- Cypher — для Neo4j.
- Gremlin — для графовых БД общего назначения.
Пайплайн аналогичен Text-to-SQL:
- NL запрос → LLM генерирует SPARQL/Cypher.
- Выполнение запроса к графовой БД.
- Результат (список узлов, путей, агрегаций) → контекст для LLM.
Запрос: «Какие фильмы снял режиссёр Кристофер Нолан?»
Промпт содержит схему графа: узлы Person (name), Movie (title), отношение DIRECTED. LLM генерирует:
MATCH (p:Person {name: 'Christopher Nolan'})-[:DIRECTED]->(m:Movie)
RETURN m.title
Результат: ["Inception", "Interstellar", "Tenet", ...] → LLM отвечает списком.
5. Проблемы и решения для KG
| Проблема | Описание | Решение |
|---|---|---|
| Сложность онтологии | Схема графа может быть большой (сотни типов узлов и отношений) | Предоставлять LLM только релевантную часть схемы (через ретривер по метаданным) |
| Неоднозначность сущностей | «Нолан» — может быть Кристофер Нолан или Джонатан Нолан | Использовать entity linking (сопоставление с URI), уточняющий вопрос |
| Производительность | Сложные графовые запросы могут выполняться долго | Индексировать узлы по имени, ограничивать глубину обхода, кэшировать частые запросы |
| Альтернатива — GraphRAG | Вместо генерации запросов — эмбеддинги графа и поиск по ним | Использовать графовые нейросети (GNN) для создания эмбеддингов подграфов, затем векторный поиск |
GraphRAG (например, Microsoft GraphRAG) — гибридный подход: граф разбивается на сообщества, для каждого строится текстовое описание, которое индексируется векторно. Это упрощает retrieval, но теряет точность формальных запросов.
6. Сравнение подходов: SQL vs Knowledge Graph
| Характеристика | SQL (реляционные БД) | Knowledge Graph |
|---|---|---|
| Модель данных | Таблицы, строки, колонки | Узлы, рёбра, свойства |
| Язык запросов | SQL (ANSI, диалекты) | SPARQL, Cypher, Gremlin |
| Типичные вопросы | Агрегации, фильтры, JOIN по ключам | Пути, связи, транзитивные зависимости |
| Сложность генерации | Средняя (схема фиксирована) | Высокая (онтология сложнее) |
| Производительность | Высокая (индексы, оптимизаторы) | Зависит от размера графа, глубины обхода |
| Пример использования | «Средний чек по магазинам за прошлый месяц» | «Какие лекарства взаимодействуют с аспирином?» |
7. Интеграция с RAG: как использовать результаты
Результаты SQL/KG запроса — это не готовый ответ, а структурированный контекст. LLM должна его интерпретировать и сформулировать ответ на естественном языке.
Пример для SQL:
Контекст (результат SQL):
| customer_name | total_orders |
|---------------|--------------|
| Иван | [[15. Какие embedding-модели вы использовали и почему\|15]] |
| Мария | [[22. Какие методы fine-tuning вы знаете и какой используете чаще всего\|22]] |
Вопрос: "Сколько заказов сделал Иван?"
Ответ: Иван сделал 15 заказов.
Для KG:
Контекст (пути графа):
- Кристофер Нолан -> режиссёр -> "Inception"
- Кристофер Нолан -> режиссёр -> "Interstellar"
Вопрос: "Какие фильмы снял Нолан?"
Ответ: Кристофер Нолан снял фильмы "Inception" и "Interstellar".
Форматирование критично: таблицы Markdown, списки, JSON — в зависимости от задачи.
8. Инструменты и фреймворки
- LangChain: SQLDatabaseChain,
SQLDatabaseAgent, GraphCypherQAChain — готовые цепочки для Text-to-SQL и Cypher. - LlamaIndex: SQLTableNodeMapping, KnowledgeGraphIndex — интеграция с SQL и Neo4j.
- Vanna.ai: специализированный Text-to-SQL с возможностью fine-tuning на собственных данных.
- Neo4j + LLM: плагин
neo4j-graphrag-python, интеграция с OpenAI. - SPARQLWrapper: для выполнения SPARQL-запросов из Python.
from langchain_community.utilities import SQLDatabase
from langchain.chains import create_sql_query_chain
from langchain_openai import ChatOpenAI
db = SQLDatabase.from_uri("sqlite:///chinook.db")
llm = ChatOpenAI(model="gpt-4", temperature=0)
chain = create_sql_query_chain(llm, db)
question = "Сколько треков в альбоме 'Let There Be Rock'?"
result = chain.invoke({"question": question})
print(result) # SQL-запрос
# Затем выполнить и получить ответ
9. Оценка качества retrieval для структурированных данных
Традиционные метрики (Hit Rate, MRR) здесь не работают, потому что результат — не набор документов, а точный ответ на запрос.
Метрики для Text-to-SQL:
- Execution Accuracy — доля запросов, для которых результат выполнения SQL совпадает с эталонным (золотой стандарт).
- Exact Set Match — совпадение множества строк (порядок не важен).
- Valid Efficiency Score — учитывает и корректность, и производительность.
Метрики для KG:
- F1 по узлам/рёбрам — насколько найденный подграф совпадает с эталонным.
- Path Accuracy — точность восстановления пути в графе.
Оценка требует размеченного датасета (NL-запрос → правильный SQL/SPARQL → ожидаемый результат). Популярные бенчмарки: Spider (SQL), LC-QuAD 2.0 (SPARQL).
Пет-проект для закрепления
Задача: Создать агента, который отвечает на вопросы по SQLite-базе данных (например, Chinook — музыкальный магазин) и по небольшому графу знаний (например, FilmKG — фильмы, актёры, режиссёры).
Инструменты: Python, LangChain, SQLite, Neo4j (или in-memory граф через NetworkX + SPARQLWrapper для имитации).
Шаги:
- Загрузить Chinook (SQLite) и создать граф FilmKG (можно в Neo4j Desktop или AuraDB).
- Написать функцию, которая по NL-запросу генерирует SQL через LangChain
create_sql_query_chain. - Реализовать выполнение SQL с обработкой ошибок (повторная генерация при неудаче).
- Для графа — аналогично через
GraphCypherQAChain. - Объединить в одного агента с маршрутизацией: если вопрос про таблицы → SQL, если про связи → KG.
- Протестировать на 10 вопросах разного типа.
Ожидаемый результат: Агент, который на естественном языке возвращает точные данные из обеих БД, например:
- «Сколько треков в жанре Rock?» → SQL → «125 треков».
- «Какие фильмы снял Стивен Спилберг?» → Cypher → «Список: "Список Шиндлера", "Парк Юрского периода"...».
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 376 | Проектирование агентных RAG-систем |
| 378 | Гибридный поиск (векторный + ключевой) |
| 379 | Маршрутизация запросов в RAG |
| 380 | Мультимодальный retrieval |
| 381 | Кэширование результатов retrieval |
| 382 | Оценка качества RAG-системы |
Навигация
- Предыдущий: 376
- Следующий: 378
- Индекс: 00. Индекс разборов