0Pricing
AI Engineering Academy · Урок

Создание интерфейса базы данных на естественном языке

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

«Создание интерфейса базы данных на естественном языке» — бесплатный урок AI Engineering Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Engineering Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Engineering Academy содержит 4 уроков всего.

Естественный язык в SQL: видение

Представьте, что Вы спрашиваете базу данных: «Какие клиенты потратили больше 1 000 долларов в прошлом месяце?» — и получаете ответ, не написав ни одного SQL-запроса. Интерфейс к базе данных на естественном языке использует вызов функций: LLM генерирует SQL, Ваше приложение безопасно выполняет его, а модель описывает результаты обычным языком. Этот подход делает доступ к данным удобным для пользователей без технической подготовки.

Обзор архитектуры системы

Конвейер NL-to-SQL состоит из четырёх взаимодействующих компонентов:

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

Определение инструмента запроса к базе данных

Определите функцию query_database, которая принимает инструкцию SQL SELECT. Описание схемы в определении функции сообщает модели, какие таблицы и столбцы доступны, поэтому она создаёт точные запросы, не делая предположений.

query_db_tool = {
    'type': 'function',
    'function': {
        'name': 'query_database',
        'description': '''Execute a read-only SQL query on the company database.
Use this to answer questions about customers, orders, and products.
Only SELECT statements are allowed. Never use DROP, DELETE, UPDATE, or INSERT.

Available tables:
- customers (id, name, email, created_at, country)
- orders (id, customer_id, total_amount, status, created_at)
- order_items (id, order_id, product_id, quantity, unit_price)
- products (id, name, category, price, stock_quantity)
''',
        'parameters': {
            'type': 'object',
            'properties': {
                'sql': {
                    'type': 'string',
                    'description': 'A valid PostgreSQL SELECT statement.'
                },
                'explanation': {
                    'type': 'string',
                    'description': 'One-sentence explanation of what this query does.'
                }
            },
            'required': ['sql', 'explanation']
        }
    }
}

Безопасное выполнение SQL

Никогда не выполняйте необработанный SQL, полученный от модели, без проверки. Реализуйте уровень безопасности, который разрешает только инструкции SELECT, отклоняет опасные ключевые слова, ограничивает количество строк результата для предотвращения проблем с памятью и выполняет запрос в транзакции базы данных, доступной только для чтения. Многоуровневая защита критически важна при выполнении кода, сгенерированного LLM.

import re
import psycopg2

DANGEROUS_KEYWORDS = ['DROP', 'DELETE', 'UPDATE', 'INSERT', 'TRUNCATE', 'ALTER', 'CREATE', 'EXEC']

def execute_safe_query(sql: str, max_rows: int = 100) -> list:
    '''Execute a read-only SQL query with safety guards.'''
    sql_upper = sql.upper().strip()

    # Only allow SELECT
    if not sql_upper.startswith('SELECT'):
        raise ValueError('Only SELECT statements are allowed.')

    # Block dangerous keywords
    for keyword in DANGEROUS_KEYWORDS:
        if re.search(r'\b' + keyword + r'\b', sql_upper):
            raise ValueError(f'Forbidden keyword: {keyword}')

    conn = psycopg2.connect('postgresql://readonly_user:pass@localhost/appdb')
    with conn:
        with conn.cursor() as cur:
            # Enforce read-only transaction
            cur.execute('SET TRANSACTION READ ONLY')
            cur.execute(sql)
            columns = [desc[0] for desc in cur.description]
            rows = cur.fetchmany(max_rows)
    return [dict(zip(columns, row)) for row in rows]

Добавление контекста схемы в системный запрос

Модель генерирует более качественный SQL, когда видит полную схему базы данных. Создайте системный запрос, включающий определения таблиц, имена и типы столбцов, а также примеры значений для категориальных столбцов. Благодаря этому модель понимает, нужно ли использовать country = 'US' или country_code = 'US', и не делает предположений.

SYSTEM_PROMPT = '''You are a data analyst assistant with access to the company database.
When users ask data questions, use the query_database tool to look up the answer.
Always explain your query in plain English before executing it.

Database schema:

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name VARCHAR NOT NULL,
    email VARCHAR UNIQUE,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    country VARCHAR(2)  -- ISO 2-letter code: 'US', 'UK', 'DE', etc.
);

CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    customer_id INTEGER REFERENCES customers(id),
    total_amount NUMERIC(10,2),
    status VARCHAR  -- 'pending', 'shipped', 'delivered', 'cancelled'
    created_at TIMESTAMPTZ DEFAULT NOW()
);

Only use columns that exist in the schema above.
'''

Форматирование результатов запроса для модели

Необработанные результаты базы данных (списки словарей) необходимо преобразовать в удобный для чтения текст, прежде чем отправлять их обратно модели. Преобразуйте набор результатов в компактное представление — таблицу или сводку JSON, — на которое модель сможет ссылаться при описании ответа. Не отправляйте тысячи строк: большие наборы результатов следует обобщать.

import json

def format_results(rows: list, max_display: int = 20) -> str:
    if not rows:
        return 'The query returned no results.'

    total = len(rows)
    display = rows[:max_display]

    # Format as a simple table
    if display:
        columns = list(display[0].keys())
        lines = [' | '.join(columns)]
        lines.append('-' * len(lines[0]))
        for row in display:
            lines.append(' | '.join(str(row[col]) for col in columns))

    result = '\n'.join(lines)
    if total > max_display:
        result += f'\n... ({total - max_display} more rows not shown)'
    return result

Полная реализация конвейера

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

from openai import OpenAI
import json

client = OpenAI()

def answer_data_question(user_question: str) -> str:
    messages = [
        {'role': 'system', 'content': SYSTEM_PROMPT},
        {'role': 'user', 'content': user_question}
    ]

    # First call: get SQL from model
    resp = client.chat.completions.create(
        model='gpt-4o', messages=messages, tools=[query_db_tool]
    )
    assistant_msg = resp.choices[0].message
    messages.append(assistant_msg)

    if resp.choices[0].finish_reason == 'tool_calls':
        tc = assistant_msg.tool_calls[0]
        args = json.loads(tc.function.arguments)
        print(f'Executing: {args["explanation"]}')
        print(f'SQL: {args["sql"]}')

        try:
            rows = execute_safe_query(args['sql'])
            result_text = format_results(rows)
        except ValueError as e:
            result_text = f'Query blocked: {str(e)}'

        messages.append({'role': 'tool', 'tool_call_id': tc.id, 'content': result_text})

        # Second call: narrate results
        final = client.chat.completions.create(model='gpt-4o', messages=messages)
        return final.choices[0].message.content

    return assistant_msg.content

Обработка многоэтапных вопросов о данных

Для сложных вопросов может потребоваться несколько запросов. Запрос «Кто входит в нашу пятёрку лучших клиентов по выручке и каковы их самые последние заказы?» требует двух запросов: сначала нужно найти лучших клиентов, затем получить их заказы. Разрешите модели выполнять несколько последовательных вызовов инструментов, запуская цикл диспетчеризации несколько раз, пока finish_reason='stop'.

def answer_complex_question(user_question: str) -> str:
    messages = [
        {'role': 'system', 'content': SYSTEM_PROMPT},
        {'role': 'user', 'content': user_question}
    ]

    for _ in range(5):  # Max 5 query rounds
        resp = client.chat.completions.create(
            model='gpt-4o', messages=messages, tools=[query_db_tool]
        )
        msg = resp.choices[0].message
        messages.append(msg)

        if resp.choices[0].finish_reason == 'stop':
            return msg.content  # Model is done

        # Process tool call and loop
        tc = msg.tool_calls[0]
        args = json.loads(tc.function.arguments)
        try:
            rows = execute_safe_query(args['sql'])
            result = format_results(rows)
        except Exception as e:
            result = f'Error: {str(e)}'

        messages.append({'role': 'tool', 'tool_call_id': tc.id, 'content': result})

    return 'Could not complete the analysis within the step limit.'

Предотвращение рисков SQL-инъекций

Даже при наличии ограничения только на SELECT хитрая модель (или злоумышленник) может попытаться извлечь данные с помощью подзапросов или уловок с комментариями. Дополнительные меры защиты включают использование пользователя базы данных с доступом только для чтения и разрешением только SELECT, выполнение в отдельном пуле соединений и проверку того, что имена таблиц в запросе соответствуют списку разрешённых имён из Вашей схемы.

ALLOWED_TABLES = {'customers', 'orders', 'order_items', 'products'}

def validate_tables_in_sql(sql: str) -> bool:
    '''Check that only whitelisted tables are referenced in the query.'''
    import sqlparse
    parsed = sqlparse.parse(sql)[0]
    table_names = set()
    from_seen = False
    for token in parsed.flatten():
        if token.ttype is sqlparse.tokens.Keyword and token.value.upper() in ('FROM', 'JOIN'):
            from_seen = True
        elif from_seen and token.ttype is sqlparse.tokens.Name:
            table_names.add(token.value.lower())
            from_seen = False
    unknown = table_names - ALLOWED_TABLES
    if unknown:
        raise ValueError(f'References unknown tables: {unknown}')
    return True

Кэширование распространённых запросов

Многие деловые вопросы задают неоднократно и получают один и тот же ответ: «Сколько у нас клиентов?» или «Какой была выручка в прошлом месяце?» Кэшируйте эти результаты в Redis с небольшим TTL. Проверяйте кэш перед выполнением запроса — это снижает нагрузку на базу данных и ускоряет ответы на распространённые аналитические вопросы.

import redis
import hashlib
import json

r = redis.Redis.from_url('redis://localhost:6379')

def cached_query(sql: str, ttl_seconds: int = 300) -> list:
    cache_key = 'nl_query:' + hashlib.sha256(sql.encode()).hexdigest()
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)
    rows = execute_safe_query(sql)
    r.setex(cache_key, ttl_seconds, json.dumps(rows, default=str))
    return rows

Объяснение запросов пользователям

Повышайте доверие пользователей, показывая им сгенерированный SQL-запрос вместе с ответом на естественном языке. Когда пользователи видят «Я выполнил этот запрос: SELECT COUNT(*) FROM customers WHERE country = ?UK?», они могут проверить правильность ответа и изучить шаблоны SQL. Поле explanation в схеме нашего инструмента идеально подходит для этого.

Быстрая проверка

Проверьте, насколько хорошо Вы поняли создание интерфейса к базе данных на естественном языке.

Итоги урока

В этом уроке Вы узнали: схема инструмента query_database добавляет контекст схемы, чтобы модель генерировала точный SQL, проверка безопасности должна блокировать инструкции, отличные от SELECT, и опасные ключевые слова до выполнения, а цикл вызовов модели позволяет проводить многоэтапный анализ данных, требующий последовательных запросов. Далее мы рассмотрим протокол контекста модели (MCP) — открытый стандарт для подключения ИИ к внешним инструментам.

Часто задаваемые вопросы

Урок «Создание интерфейса базы данных на естественном языке» бесплатный?

Да — полный текст урока «Создание интерфейса базы данных на естественном языке» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Engineering Academy, подпишись на CoddyKit PRO. Курс AI Engineering Academy содержит 4 уроков всего.

Чему я научусь в уроке «Создание интерфейса базы данных на естественном языке»?

Создайте систему, в которой пользователи задают вопросы на обычном английском языке, модель генерирует SQL с помощью вызова функций, приложение безопасно выполняет запрос, а модель описывает результа… Ты практикуешь AI Engineering Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать AI Engineering Academy?

Предыдущий опыт не требуется. AI Engineering Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.

Сколько времени занимает урок «Создание интерфейса базы данных на естественном языке»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке AI Engineering Academy?

Да. Каждый урок AI Engineering Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Определение схем функций для API
  2. Обработка вызовов инструментов в приложении
  3. Параллельный вызов функций
  4. Создание интерфейса базы данных на естественном языке
← Назад к AI Engineering Academy