Как работают агенты NL-to-SQL
Внедрение схемы, генерация и выполнение запросов, форматирование результатов.
«Как работают агенты NL-to-SQL» — бесплатный урок AI Agents на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.
Что такое агент преобразования естественного языка в SQL
Агент преобразования естественного языка в SQL переводит вопросы на обычном языке в SQL-запросы, выполняет их в базе данных и возвращает ответы, понятные человеку.
Вместо написания SELECT COUNT(*) FROM orders WHERE status='pending' пользователи просто спрашивают: «Сколько у нас заказов в ожидании?»
Основная архитектура
Каждый агент преобразования естественного языка в SQL использует один и тот же конвейер:
- Внедрение схемы — внедрить структуру БД в инструкцию
- Генерация SQL с помощью LLM — модель создает запрос
- Выполнение — выполнить запрос в базе данных
- Форматирование результатов — преобразовать строки в понятный текст
- Возврат ответа — ответить пользователю
# High-level pipeline
def nl_to_sql_agent(user_question, db_connection):
schema = get_schema(db_connection)
sql = llm_generate_sql(user_question, schema)
rows = execute_query(db_connection, sql)
answer = format_results(rows, user_question)
return answerОбъяснение внедрения схемы
LLM ничего не знает о структуре вашей базы данных. Вы должны внедрять схему в каждую инструкцию, чтобы модель знала, какие таблицы и столбцы существуют.
Компактное описание схемы сообщает модели: «В таблице заказов есть столбцы: идентификатор, user_id, статус, общая сумма, created_at».
def build_schema_prompt(schema_info):
lines = []
for table in schema_info:
cols = ', '.join(
f"{c['name']} ({c['type']})"
for c in table['columns']
)
lines.append(f"Table {table['name']}: {cols}")
return '\n'.join(lines)
# Output:
# Table users: id (INT), email (VARCHAR), created_at (TIMESTAMP)
# Table orders: id (INT), user_id (INT), status (VARCHAR), total (FLOAT)
if __name__ == '__main__':
demo_schema = [
{'name': 'users', 'columns': [{'name': 'id', 'type': 'INT'}, {'name': 'email', 'type': 'VARCHAR'}]},
{'name': 'orders', 'columns': [{'name': 'id', 'type': 'INT'}, {'name': 'user_id', 'type': 'INT'}]},
]
print(build_schema_prompt(demo_schema))
Промпт для генерации SQL с помощью LLM
Промпт должен сообщать LLM три вещи: схему, вопрос и явные инструкции возвращать только корректный SQL.
Для безопасности и корректности крайне важно явно указать режим только SELECT и целевой диалект SQL (PostgreSQL, MySQL, SQLite).
SYSTEM_PROMPT = '''You are a SQL expert. Given a database schema and a question,
generate a valid {dialect} SELECT query. Return ONLY the SQL query, no explanation.
Do not use INSERT, UPDATE, DELETE, or DROP.
Schema:
{schema}
'''
def llm_generate_sql(question, schema, dialect='PostgreSQL'):
prompt = SYSTEM_PROMPT.format(schema=schema, dialect=dialect)
response = client.chat.completions.create(
model='gpt-4o',
messages=[
{'role': 'system', 'content': prompt},
{'role': 'user', 'content': question}
]
)
return response.choices[0].message.content.strip()Выполнение сгенерированного SQL
После того как LLM вернет SQL, выполните его в реальной базе данных. По возможности используйте параметризованные запросы и всегда обрабатывайте исключения: LLM может создать некорректный SQL.
Оборачивание выполнения в конструкцию try/except позволяет повторить попытку, передав LLM подсказку об ошибке.
import psycopg2
def execute_query(conn, sql):
try:
with conn.cursor() as cur:
cur.execute(sql)
columns = [desc[0] for desc in cur.description]
rows = cur.fetchmany(100) # limit rows
return {'columns': columns, 'rows': rows}
except psycopg2.Error as e:
return {'error': str(e), 'sql': sql}Форматирование результатов для пользователя
Необработанные строки из базы данных неудобны для пользователя. Агент должен преобразовать их в ответ на естественном языке.
Для небольших наборов результатов передайте строки обратно LLM для интерпретации. Для больших наборов сначала вычислите сводные статистические показатели.
def format_results(result, original_question):
if 'error' in result:
return f'Query failed: {result["error"]}'
rows = result['rows']
columns = result['columns']
if not rows:
return 'No results found.'
# For simple counts/aggregates — just return the value
if len(columns) == 1 and len(rows) == 1:
return f'Result: {rows[0][0]}'
# For multi-row results — summarize
summary = f'Found {len(rows)} rows.\n'
for row in rows[:5]: # show first 5
summary += ', '.join(f'{columns[i]}: {row[i]}' for i in range(len(columns))) + '\n'
return summary
if __name__ == '__main__':
demo_result = {'rows': [[42]], 'columns': ['count']}
print(format_results(demo_result, 'How many users signed up?'))
demo_result2 = {'rows': [], 'columns': ['id']}
print(format_results(demo_result2, 'Any orders today?'))
Почему преобразование естественного языка в SQL сложно: неоднозначность
Неоднозначность — самая большая проблема. Рассмотрим вопрос: «Покажите мне лучших клиентов».
- Лучших по выручке? По количеству заказов? По давности?
- За последний месяц? За всё время?
- 10 лучших? 100 лучших?
Люди понимают контекст, а LLM делают предположения. Агентам нужны стратегии для обработки неоднозначных вопросов или уточнения их смысла.
AMBIGUITY_PROMPT = '''If the question is ambiguous, respond with JSON:
{"needs_clarification": true, "question": "your clarifying question"}
If clear, respond with the SQL query directly.
User question: {question}
'''
def generate_or_clarify(question, schema):
response = llm_call(AMBIGUITY_PROMPT.format(
question=question, schema=schema
))
if '"needs_clarification"' in response:
import json
return json.loads(response)
return {'sql': response}Почему преобразование естественного языка в SQL сложно: размер схемы
Корпоративные базы данных могут содержать сотни таблиц и тысячи столбцов. Внедрение полной схемы превысило бы размер контекстного окна LLM.
Возможные решения: поиск по схеме (встроить описания таблиц и извлечь релевантные), фильтрация таблиц (сначала спросить LLM, какие таблицы нужны) и сжатие схемы (исключить столбцы индексов и аудита).
# Two-phase approach for large schemas
def get_relevant_tables(question, all_tables):
prompt = f'''Given these tables: {all_tables}
Which 3-5 tables are most relevant to answer: "{question}"?
Return a JSON list of table names only.'''
response = llm_call(prompt)
import json
return json.loads(response)
def nl_to_sql_large_db(question, conn):
all_tables = list_all_tables(conn) # just names
relevant = get_relevant_tables(question, all_tables)
schema = get_schema_for_tables(conn, relevant)
return llm_generate_sql(question, schema)Почему преобразование естественного языка в SQL сложно: различия диалектов SQL
SQL не является универсальным. LIMIT в PostgreSQL и MySQL превращается в TOP в SQL Server. Функции для работы с датами различаются в разных базах данных. LLM должна знать, какой диалект использовать.
Всегда указывайте целевой диалект в системной инструкции и рассмотрите возможность добавления примеров для конкретного диалекта в подсказки с несколькими примерами.
DIALECT_EXAMPLES = {
'postgresql': 'Use LIMIT for row limits. Use NOW() for current time.',
'mysql': 'Use LIMIT for row limits. Use NOW() for current time.',
'sqlite': 'Use LIMIT. Use datetime("now") for current time.',
'mssql': 'Use TOP N for row limits. Use GETDATE() for current time.',
'bigquery': 'Use LIMIT. Use CURRENT_TIMESTAMP() for current time. Use backtick for table names.'
}
def get_dialect_hint(dialect):
return DIALECT_EXAMPLES.get(dialect.lower(), '')
if __name__ == '__main__':
for dialect in ['postgresql', 'sqlite', 'mssql']:
print(f'{dialect}: {get_dialect_hint(dialect)}')
Цикл восстановления после ошибок
Сгенерированный SQL часто не выполняется с первой попытки. Надежный агент реализует цикл восстановления после ошибок: отправляет LLM неудачный SQL и сообщение об ошибке и просит исправить запрос.
Ограничьте количество повторных попыток двумя–тремя, чтобы избежать бесконечных циклов для запросов, которые невозможно исправить.
def nl_to_sql_with_retry(question, schema, conn, max_retries=3):
sql = llm_generate_sql(question, schema)
for attempt in range(max_retries):
result = execute_query(conn, sql)
if 'error' not in result:
return format_results(result, question)
# Ask LLM to fix the error
fix_prompt = f'The SQL query failed with error: {result["error"]}\n'\
f'Original SQL: {sql}\n'\
f'Please fix the SQL query.'
sql = llm_call(fix_prompt)
print(f'Retry {attempt + 1} with fixed SQL')
return 'Could not generate a valid query after retries.'Объединение всех компонентов
Рабочий агент преобразования естественного языка в SQL объединяет все компоненты: извлечение схемы, построение промпта, генерацию SQL, проверку, выполнение, восстановление после ошибок и форматирование результатов.
Добавление кэширования запросов (один и тот же вопрос → один и тот же SQL) значительно сокращает задержку и расходы на LLM для повторяющихся запросов.
import hashlib
query_cache = {}
def cached_nl_to_sql(question, schema_hash, conn):
cache_key = hashlib.md5((question + schema_hash).encode()).hexdigest()
if cache_key in query_cache:
print('Cache hit!')
sql = query_cache[cache_key]
else:
schema = get_schema(conn)
sql = llm_generate_sql(question, schema)
query_cache[cache_key] = sql
result = execute_query(conn, sql)
return format_results(result, question)Проверка знаний
Каков правильный порядок действий в конвейере агента преобразования естественного языка в SQL?
Итоги: архитектура преобразования естественного языка в SQL
Агенты преобразования естественного языка в SQL превращают вопросы на естественном языке в выполняемые SQL-запросы с помощью структурированного конвейера: внедрить схему → сгенерировать SQL → execute → format → вернуть.
Основные проблемы — неоднозначность вопросов пользователей, большой размер схем, превышающий контекстные окна, и различия диалектов SQL в разных базах данных. Циклы восстановления после ошибок обрабатывают сгенерированный LLM SQL, который не выполняется с первой попытки.
Часто задаваемые вопросы
Урок «Как работают агенты NL-to-SQL» бесплатный?
Да — полный текст урока «Как работают агенты NL-to-SQL» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.
Чему я научусь в уроке «Как работают агенты NL-to-SQL»?
Внедрение схемы, генерация и выполнение запросов, форматирование результатов. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Agents?
Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Как работают агенты NL-to-SQL»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Agents?
Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Как работают агенты NL-to-SQL
- Понимание и внедрение схемы
- Генерация и проверка SQL-запросов
- Обработка неоднозначных вопросов к базе данных