Понимание и внедрение схемы
Извлечение и форматирование схемы DB для контекста LLM: таблицы, столбцы и связи.
«Понимание и внедрение схемы» — бесплатный урок AI Agents на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения AI Agents, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс AI Agents содержит 4 уроков всего.
Почему важен контекст схемы
LLM знает синтаксис SQL, но ничего не знает о вашей базе данных. Без контекста схемы она будет выдумывать названия таблиц и столбцов.
Внедрение схемы означает программное извлечение структуры вашей БД и включение ее в каждую инструкцию, благодаря чему LLM знает точные названия ваших таблиц, столбцов и типов.
Запрос к INFORMATION_SCHEMA
Все основные реляционные базы данных предоставляют метаданные через INFORMATION_SCHEMA. Вы можете запросить их, чтобы получить сведения о каждой таблице, имени столбца и типе данных, не обращаясь к коду приложения.
Это работает в PostgreSQL, MySQL, SQL Server и SQLite, с небольшими различиями.
import psycopg2
def get_schema(conn):
query = '''
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public'
ORDER BY table_name, ordinal_position
'''
with conn.cursor() as cur:
cur.execute(query)
return cur.fetchall()Группировка столбцов по таблицам
Необработанный результат INFORMATION_SCHEMA представляет собой плоский список строк. Сгруппируйте их по имени таблицы, чтобы создать структурированное представление, которое проще преобразовать в формат для промпта.
from collections import defaultdict
def build_schema_dict(conn):
rows = get_schema(conn)
schema = defaultdict(list)
for table_name, column_name, data_type in rows:
schema[table_name].append({
'name': column_name,
'type': data_type
})
return dict(schema)
# Result:
# {
# 'users': [{'name': 'id', 'type': 'integer'}, {'name': 'email', 'type': 'character varying'}],
# 'orders': [{'name': 'id', 'type': 'integer'}, {'name': 'user_id', 'type': 'integer'}]
# }Форматирование схемы для промптов LLM
LLM читает схему как обычный текст. Используйте краткий и понятный формат: одна таблица в строке, имена столбцов и типы — в скобках.
Указание первичных ключей (PK) и внешних ключей (FK) помогает LLM правильно составлять операторы JOIN.
def format_schema_for_prompt(schema_dict, pk_info=None, fk_info=None):
lines = []
for table, columns in schema_dict.items():
col_parts = []
for col in columns:
label = col['name']
if pk_info and (table, col['name']) in pk_info:
label += ' PK'
if fk_info and (table, col['name']) in fk_info:
label += f' FK->{fk_info[(table, col["name"])]}'
col_parts.append(f"{label} ({col['type']})")
lines.append(f"Table {table}: {', '.join(col_parts)}")
return '\n'.join(lines)
# Output:
# Table users: id PK (integer), email (varchar), created_at (timestamp)
# Table orders: id PK (integer), user_id FK->users.id (integer), total (float)
if __name__ == '__main__':
demo_schema = {'users': [{'name': 'id', 'type': 'integer'}, {'name': 'email', 'type': 'varchar'}]}
demo_pk = {('users', 'id')}
print(format_schema_for_prompt(demo_schema, pk_info=demo_pk))
Добавление первичных и внешних ключей
Связи внешних ключей — самая важная часть контекста схемы: они показывают LLM, как составлять JOIN. Запросите information_schema.table_constraints и key_column_usage, чтобы извлечь эти связи.
def get_foreign_keys(conn):
query = '''
SELECT
kcu.table_name,
kcu.column_name,
ccu.table_name AS foreign_table,
ccu.column_name AS foreign_column
FROM information_schema.table_constraints AS tc
JOIN information_schema.key_column_usage AS kcu
ON tc.constraint_name = kcu.constraint_name
JOIN information_schema.constraint_column_usage AS ccu
ON ccu.constraint_name = tc.constraint_name
WHERE tc.constraint_type = 'FOREIGN KEY'
'''
with conn.cursor() as cur:
cur.execute(query)
return {
(row[0], row[1]): f'{row[2]}.{row[3]}'
for row in cur.fetchall()
}
if __name__ == '__main__':
class FakeCursor:
def __enter__(self): return self
def __exit__(self, *a): return False
def execute(self, query): pass
def fetchall(self):
return [('orders', 'user_id', 'users', 'id')]
class FakeConn:
def cursor(self): return FakeCursor()
fks = get_foreign_keys(FakeConn())
print('Foreign keys found:')
for (table, col), ref in fks.items():
print(f' {table}.{col} -> {ref}')
Сжатие схемы: проблема
В настоящей корпоративной базе данных может быть более 200 таблиц. Если внедрить полную схему, вы превысите контекстное окно GPT-4 и потратите средства на ненужные токены.
Схема из 200 таблиц с 20 столбцами в каждой — это примерно 40 000+ токенов, что слишком дорого для отправки с каждым запросом.
def estimate_schema_tokens(schema_dict):
text = format_schema_for_prompt(schema_dict)
# Rough estimate: 1 token per 4 characters
estimated_tokens = len(text) // 4
print(f'Tables: {len(schema_dict)}')
print(f'Estimated schema tokens: {estimated_tokens}')
return estimated_tokens
# 200 tables * 15 columns * 25 chars/col = 75,000 chars = ~18,750 tokens
# Plus user question + system prompt = easily over context limitСжатие схемы: выборочное внедрение
Самая эффективная стратегия сжатия: внедрять только таблицы, относящиеся к вопросу. Используйте двухэтапный подход: сначала спросите LLM, какие таблицы ей нужны, а затем внедрите схемы только этих таблиц.
def select_relevant_tables(question, all_table_names, n=5):
table_list = ', '.join(all_table_names)
prompt = f'''Database tables: {table_list}
Question: {question}
List the {n} most relevant table names as a JSON array.
Example: ["users", "orders", "products"]'''
response = llm_call(prompt)
import json
return json.loads(response)
def compressed_schema(question, conn):
all_tables = list(build_schema_dict(conn).keys())
relevant = select_relevant_tables(question, all_tables)
full_schema = build_schema_dict(conn)
return {t: full_schema[t] for t in relevant if t in full_schema}Сжатие схемы: исключение шумовых столбцов
Во многих таблицах есть столбцы аудита, такие как created_at, updated_at, deleted_at, version, created_by, которые редко имеют значение для деловых запросов. Исключите их, чтобы уменьшить количество токенов.
AUDIT_COLUMNS = {
'created_at', 'updated_at', 'deleted_at', 'created_by',
'updated_by', 'version', 'is_deleted', 'modified_at'
}
def compress_schema(schema_dict, exclude_audit=True):
compressed = {}
for table, columns in schema_dict.items():
# Skip internal/system tables
if table.startswith('_') or table.startswith('pg_'):
continue
if exclude_audit:
columns = [c for c in columns if c['name'] not in AUDIT_COLUMNS]
if columns: # only include if columns remain
compressed[table] = columns
return compressed
if __name__ == '__main__':
demo_schema = {
'users': [{'name': 'id', 'type': 'INT'}, {'name': 'email', 'type': 'VARCHAR'}, {'name': 'created_at', 'type': 'TIMESTAMP'}],
'pg_stat': [{'name': 'x', 'type': 'INT'}],
}
compressed = compress_schema(demo_schema)
print('Tables kept:', list(compressed.keys()))
print('users columns after compression:', [c['name'] for c in compressed['users']])
Добавление описаний таблиц
Одни только имена столбцов не всегда понятны. Добавление описаний на естественном языке, объясняющих назначение каждой таблицы, значительно повышает качество генерации SQL.
Храните описания в конфигурационном файле или в комментариях к таблицам PostgreSQL.
TABLE_DESCRIPTIONS = {
'users': 'Registered app users with authentication info',
'orders': 'Customer purchase orders',
'order_items': 'Individual line items within an order',
'products': 'Product catalog with pricing',
'payments': 'Payment transactions linked to orders'
}
def format_schema_with_descriptions(schema_dict):
lines = []
for table, columns in schema_dict.items():
desc = TABLE_DESCRIPTIONS.get(table, '')
col_str = ', '.join(f"{c['name']} ({c['type']})" for c in columns)
if desc:
lines.append(f"Table {table} ({desc}): {col_str}")
else:
lines.append(f"Table {table}: {col_str}")
return '\n'.join(lines)
if __name__ == '__main__':
demo_schema = {'users': [{'name': 'id', 'type': 'INT'}], 'orders': [{'name': 'id', 'type': 'INT'}]}
print(format_schema_with_descriptions(demo_schema))
Кэширование схемы
Схемы баз данных редко меняются. Получение INFORMATION_SCHEMA при каждом запросе увеличивает задержку и нагрузку. Кэшируйте отформатированную строку схемы и сбрасывайте кэш при изменении схемы или по истечении заданного TTL.
import time
class SchemaCache:
def __init__(self, ttl_seconds=300):
self._cache = None
self._timestamp = 0
self.ttl = ttl_seconds
def get(self, conn):
now = time.time()
if self._cache is None or (now - self._timestamp) > self.ttl:
print('Refreshing schema cache...')
schema_dict = build_schema_dict(conn)
fk_info = get_foreign_keys(conn)
self._cache = format_schema_for_prompt(schema_dict, fk_info=fk_info)
self._timestamp = now
return self._cache
schema_cache = SchemaCache(ttl_seconds=300)Полный процесс внедрения схемы
Объединение всех методов: кэшируйте сжатую схему, внедряйте её в системную инструкцию и применяйте выборочную фильтрацию таблиц для больших баз данных.
def build_sql_agent_prompt(question, conn, large_db=False):
if large_db:
schema = compressed_schema(question, conn)
schema_text = format_schema_with_descriptions(schema)
else:
schema_text = schema_cache.get(conn)
system = f'''You are a PostgreSQL expert.
Return ONLY a valid SELECT query based on this schema:
{schema_text}
Rules:
- Use only SELECT statements
- Use table aliases for clarity
- Limit results to 100 rows unless asked for all
'''
return systemПроверка знаний
Когда следует использовать выборочное внедрение таблиц вместо внедрения полной схемы?
Итоги: понимание и внедрение схемы
Эффективное внедрение схемы — основа надёжных агентов, преобразующих естественный язык в SQL. Извлекайте структуру из INFORMATION_SCHEMA, включайте связи первичных и внешних ключей и форматируйте её в виде компактного текста для LLM.
Для больших баз данных кэшируйте схему, удаляйте столбцы аудита и применяйте выборочное внедрение, чтобы передавать только таблицы, относящиеся к каждому вопросу. Описания таблиц на естественном языке дополнительно повышают качество запросов.
Изучай AI Agents с ИИ-репетитором — бесплатно
Пиши и запускай код прямо в браузере, получай мгновенную помощь от ИИ-репетитора 24/7 и продолжи учиться на сайте или в приложении.
- Курсы
- 60
- Уроки
- 239
Часто задаваемые вопросы
Урок «Понимание и внедрение схемы» бесплатный?
Да — полный текст урока «Понимание и внедрение схемы» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс AI Agents, подпишись на CoddyKit PRO. Курс AI Agents содержит 4 уроков всего.
Чему я научусь в уроке «Понимание и внедрение схемы»?
Извлечение и форматирование схемы DB для контекста LLM: таблицы, столбцы и связи. Ты практикуешь AI Agents с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать AI Agents?
Предыдущий опыт не требуется. AI Agents на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Понимание и внедрение схемы»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке AI Agents?
Да. Каждый урок AI Agents включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Как работают агенты NL-to-SQL
- Понимание и внедрение схемы
- Генерация и проверка SQL-запросов
- Обработка неоднозначных вопросов к базе данных