0Pricing
AI Agents · 课时

NL 到 SQL 代理的工作原理

模式注入、查询生成、执行和结果格式化。

NL 到 SQL 代理的工作原理 是 CoddyKit 上的免费 AI Agents 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Agents 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Agents 课程共包含 4 节课。

什么是 NL-to-SQL 代理?

自然语言转 SQL 代理会将通俗易懂的英语问题转换为 SQL 查询,针对数据库执行查询,并返回人类易读的答案。

用户无需编写 SELECT COUNT(*) FROM orders WHERE status='pending',只需提问:“我们有多少个待处理订单?”

核心架构

每个 NL-to-SQL 代理都遵循相同的流程:

  1. 模式注入——将数据库结构注入提示
  2. LLM 生成 SQL——模型生成查询
  3. 执行——针对数据库运行查询
  4. 格式化结果——将行转换为易读的文本
  5. 返回答案——向用户作答
# 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 不知道您的数据库结构。您必须将模式注入每个提示,让模型知道有哪些表和列。

简洁的模式描述会告诉模型:“表 orders 包含以下列: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))

LLM SQL 生成提示

提示必须向 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?'))

NL-to-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}

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

NL-to-SQL 为何困难:SQL 方言差异

SQL 并不通用。在 PostgreSQL/MySQL 中使用的 LIMIT,在 SQL Server 中会变成 TOP。不同数据库的日期函数也各不相同。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 往往第一次执行就会失败。健壮的代理会实现错误恢复循环:将失败的 SQL 和错误消息发回 LLM,请它修复查询。

将重试次数限制为 2 至 3 次,避免无法修复的查询导致无限循环。

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

整合所有部分

生产级 NL-to-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)

知识检查

NL-to-SQL 代理流程中,正确的步骤顺序是什么?

回顾:NL-to-SQL 架构

NL-to-SQL 代理通过结构化流程将自然语言问题转换为可执行的 SQL 查询:注入模式 → 生成 SQL → 执行 → 格式化 → 返回。

主要挑战包括用户问题中的歧义、超出上下文窗口的大型模式,以及不同数据库之间的 SQL 方言差异。错误恢复循环可以处理首次执行失败的 LLM 生成 SQL。

常见问题解答

「NL 到 SQL 代理的工作原理」课时是免费的吗?

是的 — 「NL 到 SQL 代理的工作原理」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Agents 课程的其余内容,请升级到 CoddyKit PRO。 AI Agents 课程共包含 4 节课。

「NL 到 SQL 代理的工作原理」这节课中我会学到什么?

模式注入、查询生成、执行和结果格式化。 你通过在浏览器中直接运行的动手代码来练习 AI Agents,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 AI Agents 需要有经验吗?

无需任何先前经验。CoddyKit 上的 AI Agents 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「NL 到 SQL 代理的工作原理」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 AI Agents 课中编写并运行代码吗?

能。每节 AI Agents 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. NL 到 SQL 代理的工作原理
  2. 理解与注入模式
  3. 生成并验证 SQL 查询
  4. 处理含糊的数据库问题
← 返回 AI Agents