0Pricing
AI Agents · Lección

Cómo funcionan los agentes NL-to-SQL

Inyección del esquema, generación y ejecución de consultas y formato de resultados.

Cómo funcionan los agentes NL-to-SQL es una lección gratuita de AI Agents en CoddyKit. Esta es la lección 1 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.

¿Qué es un agente NL-to-SQL?

Un agente de lenguaje natural a SQL traduce preguntas en lenguaje natural a consultas SQL, las ejecuta en una base de datos y devuelve respuestas comprensibles para las personas.

En lugar de escribir SELECT COUNT(*) FROM orders WHERE status='pending', los usuarios simplemente preguntan: "¿Cuántos pedidos pendientes tenemos?"

La arquitectura principal

Todos los agentes NL-to-SQL siguen la misma canalización:

  1. Inyección del esquema — inyectar la estructura de la BD en el prompt
  2. El LLM genera SQL — el modelo produce una consulta
  3. Ejecución — ejecutar la consulta en la base de datos
  4. Formateo de resultados — convertir las filas en texto legible
  5. Devolución de la respuesta — responder al usuario
# 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

Explicación de la inyección del esquema

El LLM no conoce la estructura de su base de datos. Debe inyectar el esquema en cada prompt para que el modelo sepa qué tablas y columnas existen.

Una descripción compacta del esquema indica al modelo: "La tabla orders tiene las columnas: id, user_id, status, total, 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))

Prompt para la generación de SQL por el LLM

El prompt debe proporcionar al LLM tres elementos: el esquema, la pregunta e instrucciones explícitas para que devuelva únicamente SQL válido.

Es fundamental especificar que solo se permiten consultas SELECT y cuál es el dialecto SQL de destino (PostgreSQL, MySQL o SQLite), tanto por seguridad como por corrección.

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

Ejecución del SQL generado

Después de que el LLM devuelva el SQL, ejecútelo en la base de datos real. Use consultas parametrizadas cuando sea posible y capture siempre las excepciones, ya que el LLM puede generar SQL no válido.

Envolver la ejecución en un bloque try/except permite reintentarlo enviando al LLM una indicación sobre el error.

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}

Formateo de los resultados para el usuario

Las filas sin procesar de una base de datos no son fáciles de usar. El agente debe convertirlas en una respuesta en lenguaje natural.

Para conjuntos de resultados pequeños, devuelva las filas al LLM para que las interprete. Para conjuntos grandes, calcule primero estadísticas resumidas.

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

Por qué NL-to-SQL es difícil: ambigüedad

La ambigüedad es el mayor desafío. Considere la pregunta: "Muéstreme los mejores clientes."

  • ¿Los mejores por ingresos, por número de pedidos o por actualidad?
  • ¿Del último mes o de todo el período?
  • ¿Los 10 mejores o los 100 mejores?

Las personas entienden el contexto; los LLM hacen suposiciones. Los agentes necesitan estrategias para gestionar o aclarar las preguntas ambiguas.

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}

Por qué NL-to-SQL es difícil: tamaño del esquema

Las bases de datos empresariales pueden tener cientos de tablas y miles de columnas. Inyectar el esquema completo superaría la ventana de contexto del LLM.

Entre las soluciones se incluyen la búsqueda de esquemas (generar embeddings de las descripciones de las tablas y recuperar las relevantes), el filtrado de tablas (preguntar primero al LLM qué tablas necesita) y la compresión del esquema (omitir las columnas de índices y auditoría).

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

Por qué NL-to-SQL es difícil: diferencias entre dialectos SQL

SQL no es universal. LIMIT en PostgreSQL/MySQL se convierte en TOP en SQL Server. Las funciones de fecha varían entre bases de datos. El LLM debe saber qué dialecto debe utilizar.

Incluya siempre el dialecto de destino en el prompt del sistema y considere añadir ejemplos específicos del dialecto mediante prompting few-shot.

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

Bucle de recuperación ante errores

El SQL generado suele fallar en el primer intento. Un agente sólido implementa un bucle de recuperación ante errores: envía al LLM el SQL fallido y el mensaje de error, y le pide que corrija la consulta.

Limite los reintentos a 2-3 para evitar bucles infinitos con consultas que no se pueden corregir.

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.'

Integración de todos los componentes

Un agente NL-to-SQL de producción combina todas las piezas: recuperación del esquema, construcción del prompt, generación de SQL, validación, ejecución, recuperación ante errores y formateo de resultados.

Añadir caché de consultas (misma pregunta → mismo SQL) reduce drásticamente la latencia y los costes del LLM en las consultas repetidas.

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)

Comprobación de conocimientos

¿Cuál es el orden correcto de los pasos en la canalización de un agente NL-to-SQL?

Resumen: arquitectura NL-to-SQL

Los agentes NL-to-SQL convierten preguntas en lenguaje natural en consultas SQL ejecutables mediante una canalización estructurada: inyectar el esquema → generar SQL → ejecutar → formatear → devolver.

Los principales desafíos son la ambigüedad de las preguntas de los usuarios, los esquemas grandes que superan las ventanas de contexto y las diferencias entre dialectos SQL de las distintas bases de datos. Los bucles de recuperación ante errores gestionan el SQL generado por el LLM que falla en la primera ejecución.

Preguntas frecuentes

¿La lección «Cómo funcionan los agentes NL-to-SQL» es gratis?

Sí — el texto completo de «Cómo funcionan los agentes NL-to-SQL» 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 «Cómo funcionan los agentes NL-to-SQL»?

Inyección del esquema, generación y ejecución de consultas y formato de resultados. 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 1 de 4.

¿Cuánto tiempo toma la lección «Cómo funcionan los agentes NL-to-SQL»?

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