Агентный RAG для SQL-таблиц: семантический поиск по длинным текстам в БД | AiManual
AiManual Logo Ai / Manual.
18 Фев 2026 Гайд

Агентный RAG поверх SQL-таблиц: архитектура для семантического поиска по длинным текстам в БД

Пошаговая архитектура агентного RAG поверх SQL-таблиц для семантического поиска по LONGTEXT колонкам. Решение для enterprise-данных на 2026 год.

Проблема, которая сводит с ума: тексты в SQL-таблицах

Представьте типичную enterprise-систему. У вас есть таблица customer_support_tickets с колонкой description типа LONGTEXT. Или таблица legal_documents с полем content. Или product_reviews с текстом отзыва.

Классический поиск через LIKE '%жалоба на доставку%' работает как молоток по микроскопу - грубо и бесполезно. Пользователь ищет "проблемы с поздней доставкой в Москве", а система находит только те записи, где эти слова стоят именно в таком порядке.

Статистика на февраль 2026: 78% корпоративных баз данных содержат хотя бы одну таблицу с длинными текстовыми полями, но только 12% из них имеют эффективные инструменты семантического поиска по этим данным.

Почему просто не засунуть все в векторную БД? Потому что данные живые. Они связаны с другими таблицами через foreign keys. Они обновляются каждую секунду. Вытаскивать их в отдельное хранилище - значит терять актуальность и создавать головную боль с синхронизацией.

Я видел проекты, где команды пытались решить эту проблему через кастомные триггеры на обновление векторов. Через неделю они получали race conditions и рассинхронизацию в 40% записей.

Агентный RAG: не просто поиск, а расследование

Обычный RAG берет запрос, ищет похожие чанки, склеивает контекст и генерирует ответ. Над SQL-таблицами это работает плохо по одной причине: структура.

Когда у вас текст разбит на строки с метаданными (дата создания, автор, статус, категория), простой семантический поиск по чанкам теряет всю эту контекстную информацию.

Агентный RAG - это когда система не просто ищет, а расследует. Она понимает, что нужно сначала определить тип запроса, потом выбрать стратегию поиска, возможно, скомбинировать несколько подходов, и только потом формулировать ответ.

💡
На февраль 2026 самые эффективные модели для агентного RAG - Llama 3.2 90B (для сложной логики) и Claude 3.5 Sonnet (для анализа текста). GPT-4o тоже работает, но дороже в production. Новый Gemini 2.0 Ultra показал хорошие результаты в тестах, но еще не до конца проверен в enterprise-средах.

Зачем агент, если можно написать один сложный prompt? Потому что prompt длиной в 2000 токенов начинает страдать от "потери середины". LLM забывает инструкции из центра prompt'а. Агентная архитектура разбивает логику на четкие шаги, каждый со своим контекстом.

Архитектура: четыре слоя, которые работают вместе

1 Слой классификации запросов

Первое, что делает система - понимает, что от нее хотят. Я разделяю все запросы на три категории:

  • Фактологический поиск: "Найди все тикеты с жалобами на сломанный экран iPhone 16"
  • Аналитический запрос: "Какие основные темы жалоб от клиентов из Москвы за последний месяц?"
  • Комбинированный запрос: "Покажи тикеты про проблемы с доставкой, которые были эскалированы менеджеру" (тут и семантика, и структурные фильтры)

Для классификации используем легкую модель вроде Llama 3.1 8B. Не нужно мощностей монстра - задача простая, но критически важная. Ошибка на этом этапе приводит к неправильному выбору стратегии поиска.

# Упрощенный код классификатора
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch

class QueryClassifier:
    def __init__(self):
        self.model_name = "meta-llama/Llama-3.1-8B-Instruct"
        self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
        self.model = AutoModelForCausalLM.from_pretrained(
            self.model_name,
            torch_dtype=torch.float16,
            device_map="auto"
        )
    
    def classify(self, query: str) -> str:
        prompt = f"""Classify this database query type:
        1. FACTUAL - finding specific records matching semantic meaning
        2. ANALYTICAL - summarizing, analyzing trends, themes
        3. COMBINED - both semantic and structural filters
        
        Query: {query}
        
        Return only one word: FACTUAL, ANALYTICAL, or COMBINED"""
        
        inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
        outputs = self.model.generate(**inputs, max_new_tokens=10)
        classification = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
        
        # Извлекаем только классификацию
        return classification.strip().split()[-1]

2 Слой гибридного поиска

Вот где начинается магия. Для каждой категории запросов - своя стратегия поиска.

Для фактологических запросов используем векторизацию текстов прямо в SQL. Да, вы не ослышались. Современные PostgreSQL (16+) и MySQL (9.0+) поддерживают векторные расширения. Не нужно вытаскивать данные наружу.

Важное обновление 2025 года: PostgreSQL pgvector теперь поддерживает HNSW индекс прямо в базе. Это меняет правила игры - поиск по 10 миллионам векторов занимает миллисекунды, а не секунды.

Схема работы:

  1. Берем embedding-модель (я рекомендую BAAI/bge-large-en-v2.0 или новый Cohere/embed-multilingual-v4.0 для мультиязычных данных)
  2. Создаем колонку description_embedding vector(1024) в таблице
  3. При вставке/обновлении текста автоматически генерируем embedding через триггер
  4. Ищем через ORDER BY description_embedding <-> query_embedding LIMIT 20

Для аналитических запросов нужен другой подход. Здесь мы используем агента, который:

  • Сначала делает семантический поиск для получения релевантных записей
  • Потом анализирует результаты, группирует по темам
  • Генерирует сводку, выделяя основные паттерны

Для комбинированных запросов - самое интересное. Агент разбирает запрос на части:

-- Пример как агент понимает запрос
Запрос: "Проблемы с доставкой в декабре 2025 от клиентов премиум-статуса"

1. Семантическая часть: "проблемы с доставкой" → векторный поиск
2. Структурные фильтры:
   - WHERE created_at BETWEEN '2025-12-01' AND '2025-12-31'
   - WHERE customer_tier = 'premium'
3. Объединяем: векторный поиск И структурные фильтры

3 Слой извлечения и обогащения контекста

Нашли релевантные строки? Отлично. Теперь нужно понять, что с ними делать.

Проблема в том, что найденная запись - это не изолированный текст. У нее есть связи. Тикет связан с клиентом. Документ связан с автором. Отзыв связан с продуктом.

Агентный RAG должен уметь "пройтись по связям". Нашли тикет про проблему с доставкой? Хорошо бы также получить информацию о клиенте (его история, предыдущие обращения) и о заказе (что заказывал, когда).

Я реализую это через "контекстных агентов", каждый из которых знает свою область:

Тип агента Что делает Пример SQL
Customer Context Agent Находит информацию о клиенте по customer_id SELECT * FROM customers WHERE id = ?
Order Context Agent Ищет связанные заказы SELECT * FROM orders WHERE ticket_id = ?
Timeline Agent Строит временную линию событий SELECT * FROM ticket_updates WHERE ticket_id = ? ORDER BY created_at

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

4 Слой генерации ответов с проверкой

Финальный слой - тот, который превращает найденные данные и контекст в человеческий ответ.

Здесь многие совершают ошибку: дают LLM все найденные записи и говорят "ответь на вопрос". Результат? Галлюцинации, пропуск важных деталей, неправильные выводы.

Мой подход - двухэтапная генерация:

  1. Агент-аналитик структурирует найденную информацию. Создает сводку: "Найдено 15 тикетов. 10 про позднюю доставку, 5 про поврежденные товары. 12 от премиум-клиентов."
  2. Агент-проверщик (меньшая модель) проверяет, что все факты из ответа действительно есть в исходных данных. Это guardrail против галлюцинаций.
  3. Агент-форматировщик создает финальный ответ в нужном формате (текст, таблица, JSON).
💡
С февраля 2026 в OpenAI API появилась функция "вероятности цитирования" для GPT-4o. Модель теперь может указывать, насколько она уверена в том, что определенный факт присутствует в предоставленном контексте. Используйте эту функцию для автоматической проверки ответов.

Производственный пайплайн: от прототипа к продакшену

Архитектура готова. Теперь как запустить это в production?

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

Решение: кеширование на нескольких уровнях:

  • Кеш запросов: Redis с TTL 5 минут для одинаковых запросов
  • Кеш embedding'ов: Векторизованные запросы кешируются отдельно
  • Кеш контекста: Собранный контекст по конкретным записям (обновляется при изменении данных)

Вторая проблема - обновление векторов. Когда текст в БД меняется, его embedding должен обновиться. Но делать это синхронно - убить производительность.

Используйте асинхронную очередь. Триггер в БД добавляет ID измененной записи в RabbitMQ или Kafka. Отдельный воркер берет запись, генерирует новый embedding, обновляет в фоне.

# Пример асинхронного обновления векторов
import asyncio
from redis import Redis
from sqlalchemy import create_engine
from sentence_transformers import SentenceTransformer

class VectorUpdateWorker:
    def __init__(self):
        self.redis = Redis(host='redis', port=6379)
        self.db = create_engine('postgresql://user:pass@localhost/db')
        self.model = SentenceTransformer('BAAI/bge-large-en-v2.0')
    
    async def process_queue(self):
        while True:
            # Берем ID из очереди
            record_id = self.redis.rpop('embedding_update_queue')
            if not record_id:
                await asyncio.sleep(1)
                continue
            
            # Получаем текст из БД
            text = self.db.execute(
                "SELECT description FROM tickets WHERE id = %s", 
                [record_id]
            ).fetchone()[0]
            
            # Генерируем embedding
            embedding = self.model.encode(text)
            
            # Обновляем в БД
            self.db.execute(
                """UPDATE tickets 
                   SET description_embedding = %s 
                   WHERE id = %s""",
                [embedding.tolist(), record_id]
            )

Ошибки, которые сломают вашу систему

Я видел десятки попыток внедрить подобные системы. Вот что идет не так:

Ошибка 1: Одна embedding-модель на все случаи. Технические тексты и отзывы клиентов имеют разную семантику. Используйте разные модели или хотя бы fine-tune базовую модель на ваших данных.

Ошибка 2: Игнорирование структурных фильтров. Пользователь сказал "за последний месяц", а система ищет по всем данным. Агент должен уметь выделять временные рамки, категории, статусы из запроса.

Ошибка 3: Нет лимитов на контекст. Собирать всю историю клиента по каждому запросу - перегрузка LLM и медленный ответ. Устанавливайте разумные лимиты: последние 10 тикетов, основные данные клиента и т.д.

Ошибка 4: Прямые запросы к продакшен-БД. Агенты иногда генерируют тяжелые запросы. Всегда используйте read-only реплики или выделенные инстансы для поиска.

Совет из реального инцидента: один из наших агентов сгенерировал запрос с JOIN 8 таблиц без LIMIT. Продакшен-база легла на 3 минуты. Теперь все агентные запросы идут через прокси, который добавляет LIMIT 1000 и запрещает определенные паттерны.

Интеграция с существующими системами

Ваша компания уже использует гибридный поиск или Text-to-SQL агентов? Отлично, агентный RAG поверх SQL-таблиц дополняет их, а не заменяет.

Сценарий 1: У вас есть Text-to-SQL агент для структурных запросов. Добавляем агентный RAG для семантического поиска по текстовым полям. Пользовательский запрос сначала анализируется - если нужен семантический поиск, идет в RAG, если структурный - в Text-to-SQL.

Сценарий 2: У вас есть классический RAG поверх документов. Добавляем агентный RAG для SQL-таблиц. Теперь система может искать и в документах, и в структурированных данных, понимая разницу между ними.

Ключевой момент - точность в 90% недостаточна для production. Агентная архитектура позволяет добавить проверки, guardrails и fallback-стратегии, которые поднимают надежность до приемлемого уровня.

Что будет дальше?

На февраль 2026 я вижу три тренда, которые изменят эту архитектуру:

  1. Мультимодальные embedding'и. Скоро появятся модели, которые понимают не только текст, но и структуру таблицы, типы данных, связи между колонками. Поиск "найди клиентов, которые жаловались на то же самое" будет учитывать схему данных.
  2. Агенты с долгой памятью. Сейчас каждый запрос обрабатывается изолированно. В будущем агенты будут запоминать предыдущие взаимодействия, понимать контекст диалога, учиться на исправлениях.
  3. Нативная поддержка в облачных БД. AWS Aurora и Google Cloud Spanner уже анонсировали встроенные векторные поисковые движки. Через год это будет стандартом.

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

Через месяц у вас будет система, которая делает то, что раньше требовало ручной работы аналитика. Через три месяца - production-решение, которое экономят сотни человеко-часов. А через год вы будете удивляться, как вообще жили без этого.