0Pricing
AI Agents · Lección

Comprensión e inyección de esquemas

Extraiga y dé formato al esquema de la base de datos para el contexto del LLM: tablas, columnas y relaciones.

Comprensión e inyección de esquemas es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 2 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de AI Agents, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de AI Agents incluye 4 lecciones en total.

Por qué es importante el contexto del esquema

El LLM conoce la sintaxis de SQL, pero no sabe nada sobre su base de datos. Sin contexto del esquema, inventará nombres de tablas y columnas.

La inyección del esquema consiste en extraer mediante programación la estructura de su BD e incluirla en cada prompt, de modo que el LLM conozca sus tablas, columnas y tipos exactos.

Consulta de INFORMATION_SCHEMA

Las principales bases de datos relacionales exponen metadatos mediante INFORMATION_SCHEMA. Puede consultarlo para obtener todas las tablas, los nombres de las columnas y los tipos de datos sin tocar el código de la aplicación.

Esto funciona en PostgreSQL, MySQL, SQL Server y SQLite, con pequeñas diferencias.

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()

Agrupación de columnas por tabla

El resultado sin procesar de INFORMATION_SCHEMA es una lista plana de filas. Agrúpelas por nombre de tabla para crear una representación estructurada que sea más fácil de incluir en un prompt.

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'}]
# }

Formateo del esquema para prompts del LLM

El LLM lee el esquema como texto sin formato. Use un formato conciso y legible: una tabla por línea, con los nombres de las columnas y sus tipos entre paréntesis.

Incluir las claves principales (PK) y las claves externas (FK) ayuda al LLM a escribir instrucciones JOIN correctas.

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))

Inclusión de claves principales y externas

Las relaciones entre claves externas son la parte más importante del contexto del esquema, ya que indican al LLM cómo escribir los JOIN. Consulte information_schema.table_constraints y key_column_usage para extraerlas.

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}')

Compresión del esquema: el problema

Una base de datos empresarial real puede tener más de 200 tablas. Si inyecta el esquema completo, superará la ventana de contexto de GPT-4 y gastará dinero innecesariamente en tokens.

Un esquema de 200 tablas con 20 columnas cada una ocupa aproximadamente 40.000+ tokens, un coste demasiado alto para enviarlo en cada consulta.

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

Compresión del esquema: inyección selectiva

La estrategia de compresión más eficaz consiste en inyectar únicamente las tablas relevantes para la pregunta. Use un enfoque en dos fases: primero pregunte al LLM qué tablas necesita y, después, inyecte únicamente los esquemas de esas tablas.

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}

Compresión del esquema: exclusión de columnas irrelevantes

Muchas tablas tienen columnas de auditoría como created_at, updated_at, deleted_at, version y created_by, que rara vez son relevantes para las consultas de negocio. Elimínelas para reducir el número de tokens.

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']])

Adición de descripciones de tablas

Los nombres de las columnas no siempre se explican por sí solos. Añadir descripciones en lenguaje natural de lo que representa cada tabla mejora considerablemente la calidad de la generación de SQL.

Almacene las descripciones en un archivo de configuración o como comentarios de tablas de 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))

Almacenamiento en caché del esquema

Los esquemas de bases de datos rara vez cambian. Consultar INFORMATION_SCHEMA en cada consulta añade latencia y carga. Almacene en caché la cadena de texto con formato del esquema e invalídela cuando se produzcan eventos de cambio del esquema o cuando venza un TTL basado en tiempo.

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)

Flujo completo de inyección del esquema

Combine todas las técnicas: almacene en caché el esquema comprimido, inyéctelo en el prompt del sistema y use un filtrado selectivo de tablas para bases de datos grandes.

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

Comprobación de conocimientos

¿Cuándo debe usar la inyección selectiva de tablas en lugar de inyectar el esquema completo?

Resumen: comprensión e inyección del esquema

Una inyección eficaz del esquema es la base de unos agentes NL-to-SQL fiables. Extraiga la estructura de INFORMATION_SCHEMA, incluya las relaciones entre claves primarias y foráneas, y dele formato como texto compacto para el LLM.

Para bases de datos grandes: almacene el esquema en caché, elimine las columnas de auditoría y use la inyección selectiva para enviar únicamente las tablas relevantes para cada pregunta. Las descripciones de las tablas en lenguaje natural mejoran aún más la calidad de las consultas.

Preguntas frecuentes

¿La lección «Comprensión e inyección de esquemas» es gratis?

Sí — el texto completo de «Comprensión e inyección de esquemas» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de AI Agents, actualiza a CoddyKit PRO. El curso de AI Agents incluye 4 lecciones en total.

¿Qué aprenderé en «Comprensión e inyección de esquemas»?

Extraiga y dé formato al esquema de la base de datos para el contexto del LLM: tablas, columnas y relaciones. Practicas AI Agents con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar AI Agents?

No se requiere experiencia previa. AI Agents en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 2 de 4.

¿Cuánto tiempo toma la lección «Comprensión e inyección de esquemas»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de AI Agents?

Sí. Cada lección de AI Agents incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Cómo funcionan los agentes NL-to-SQL
  2. Comprensión e inyección de esquemas
  3. Generación y validación de consultas SQL
  4. Gestión de preguntas ambiguas sobre bases de datos
← Volver a AI Agents