基于大模型的自然语言数据库查询实现指南
使用大模型(LLM)通过自然语言生成 SQL 语句,从结构化数据库中获取结果,是目前大模型与数据交互的主流形式之一。这种技术通常被称为 Text-to-SQL 或 NL2SQL(Natural Language to SQL)。它极大地降低了非技术人员访问数据的门槛,使得业务人员可以直接通过对话方式查询数据。
本文将详细介绍如何使用 Python 生态中的 LlamaIndex、SQLAlchemy 以及本地或云端大模型,构建一个支持自然语言查询数据库的系统。我们将涵盖环境搭建、数据准备、基础查询、流式输出、模糊查询优化以及安全注意事项等完整流程。
一、核心原理概述
Text-to-SQL 的基本流程如下:
- 上下文注入:将数据库的 DDL(建表语句)作为上下文信息提供给大模型,使其理解表结构、字段含义及关系。
- 意图识别与 SQL 生成:大模型接收用户的自然语言问题,结合表结构,生成对应的 SQL 查询语句。
- 执行与反馈:系统执行生成的 SQL,获取结果集,并将结果再次输入大模型,由大模型将数据转换为自然语言回答。
以下是一个简单的示例场景:存储朝阳区高中学校招生信息的数据库,用户提问 陈经纶招多少人?,系统大致处理步骤为:
- 数据库 DDL 加入对话上下文,主要是建表语句,让大模型感知表结构。
- 大模型将提示词转化为 SQL 查询语句,例如
select * from school_info where school_name like '%陈经纶%'。 - 大模型根据 SQL 查询结果,生成自然语言的回答,例如
北京市陈经纶中学招收的学生人数为 279 名。
二、环境准备与依赖安装
在开始之前,需要准备好开发环境。推荐使用 Jupyter Notebook 或 JupyterLab 进行交互式开发。
1. 基础依赖
我们需要安装以下核心库:
pip install llama-index sqlalchemy pandas ollama openai
llama-index: 用于连接 LLM 和外部数据源的核心框架。sqlalchemy: ORM 工具,用于定义数据库结构和操作记录。pandas: 用于数据处理和展示。ollama: 用于运行本地大模型。openai: 兼容 OpenAI API 格式的客户端,用于调用云端或本地兼容接口。
2. 模型配置
本文演示同时支持本地模型(通过 Ollama)和云端模型(通过 One-API 或类似网关)。
- 本地模型:需确保 Ollama 服务已启动,并拉取相应模型(如 qwen2, llama3 等)。
- 云端模型:需配置 API Key 和 Base URL。
三、数据准备与建模
我们使用 SQLAlchemy 在内存 SQLite 数据库中创建示例表和相关记录。SQLite 轻量且无需额外服务器,适合演示。
1. 建立连接和表结构
from sqlalchemy import (
create_engine,
MetaData,
Table,
Column,
String,
Integer,
insert,
)
# 创建内存数据库引擎
engine = create_engine("sqlite:///:memory:")
metadata_obj = MetaData()
# 创建学校信息表结构
table_name = "school_info"
school_info_table = Table(
table_name,
metadata_obj,
Column("school_name", String(200), primary_key=True),
Column("students_enrolled", Integer, nullable=False),
)
metadata_obj.create_all(engine)
2. 插入测试数据
rows = [
{"school_name": "北京市第八十中学", "students_enrolled": 260},
{"school_name": "北京市陈经纶中学", "students_enrolled": 279},
{"school_name": "北京市日坛中学", "students_enrolled": 403},
{"school_name": "中国人民大学附属中学朝阳学校", "students_enrolled": 247},
{"school_name": "北京工业大学附属中学", "students_enrolled": 418},
{"school_name": "北京中学", "students_enrolled": 121},
]
for row in rows:
stmt = insert(school_info_table).values(**row)
with engine.begin() as connection:
cursor = connection.execute(stmt)
3. 数据验证
可以使用 Pandas 查看数据是否正确写入:
import pandas as pd
with engine.connect() as conn:
df = pd.read_sql("SELECT * FROM school_info", conn)
print(df)
四、LlamaIndex 基础配置
在使用 LlamaIndex 进行查询前,需要设置全局的 LLM 和 Embedding 模型。这样后续方法调用时无需重复传递参数。
1. 配置 LLM
这里以 Qwen2 为例,通过 Ollama 本地部署或兼容接口调用。
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core import Settings
Settings.llm = OpenAILike(
model="qwen2",
api_base="http://localhost:11434/v1",
api_key="ollama",
is_chat_model=True,
temperature=0.1,
request_timeout=60.0
)
Settings.embed_model = OllamaEmbedding(
model_name="quentinz/bge-large-zh-v1.5",
base_url="http://localhost:11434",
ollama_additional_kwargs={"mirostat": 0}
)
*注意:如果网络不通,请确保 Ollama 服务地址正确,且防火墙允许访问。
五、基本查询实现
LlamaIndex 提供了 NLSQLTableQueryEngine,这是最基础的文本转 SQL 查询引擎。
1. 初始化查询引擎
from llama_index.core.sql_database.interface import SQLDatabase
from llama_index.core.query_engine import NLSQLTableQueryEngine
# 封装数据库对象
sql_database = SQLDatabase(engine)
query_engine = NLSQLTableQueryEngine(
sql_database=sql_database,
tables=["school_info"],
)
query_str = "招生最多的是哪个学校?"
response = query_engine.query(query_str)
print(response.response)
# 预期输出:'招生最多的是北京工业大学附属中学,共有 418 名学生。'
2. 局限性分析
这种方式虽然简单,但存在明显不足:
- 不支持流式输出:用户必须等待整个响应生成完毕才能看到结果,体验较差。
- 上下文限制:当数据库表很多时,DDL 信息可能超出 LLM 的上下文窗口,导致性能下降或错误。
- 缺乏灵活性:难以针对特定业务逻辑定制提示词。
六、支持流式输出回答
为了提升用户体验,需要使用 LlamaIndex 底层的检索 API (Retriever) 来构建支持流式的查询引擎。
1. 配置 NLSQLRetriever
from llama_index.core.retrievers import NLSQLRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
nl_sql_retriever = NLSQLRetriever(
sql_database,
tables=["school_info"],
return_raw=True
)
query_engine = RetrieverQueryEngine.from_args(
nl_sql_retriever,
streaming=True
)
response = query_engine.query("招生最多的前三个学校?")
response.print_response_stream()
2. 效果说明
启用流式后,用户可以看到文字逐字生成,大大减少了等待焦虑感。这对于长文本回答尤为重要。
七、高级功能:模糊查询与提示词工程
默认情况下,Text-to-SQL 对模糊匹配的支持较弱,往往要求精确匹配。如果需要支持模糊查询(如 LIKE '%关键词%'),需要调整 Prompt 模板。
1. 默认行为问题
直接询问 陈经纶招多少? 可能会失败,因为模型倾向于生成精确匹配条件,而表中是 北京市陈经纶中学。
2. 自定义 Prompt 模板
我们可以通过修改 text_to_sql_prompt 来强制模型使用模糊查询。
from llama_index.core.prompts import PromptTemplate
nl_sql_retriever = NLSQLRetriever(
sql_database,
tables=["school_info"],
return_raw=False,
llm=OpenAILike(
model='qwen-turbo',
api_base="http://your-api-gateway:3000/v1",
api_key="sk-your-key",
is_chat_model=True,
temperature=0.1,
request_timeout=60.0
)
)
# 获取原有模板
old_prompt_str = nl_sql_retriever.get_prompts()['text_to_sql_prompt'].template
# 添加模糊查询指令
new_prompt = PromptTemplate(
f"{old_prompt_str}\n查询关键字使用模糊查询,并且查询结果应包含关键字所属的列"
)
nl_sql_retriever.update_prompts({"text_to_sql_prompt": new_prompt})
query_engine = RetrieverQueryEngine.from_args(
nl_sql_retriever,
streaming=True,
)
response = query_engine.query("陈经纶招多少?")
response.print_response_stream()
# 预期输出:陈经纶招收 279 名学生。
3. 定制化回答格式
除了生成 SQL,我们还可以控制最终的回答格式。例如要求显示学校全名。
my_qa_prompt_template = (
"回答中要求使用学校的完整名称 (school_name)\n"
"不用再计算,给出的就是答案\n"
"Context information is below.\n"
"---------------------\n"
"{context_str}\n"
"---------------------\n"
"Given the context information and not prior knowledge, "
"answer the query.\n"
"Query: {query_str}\n"
"Answer: "
)
my_qa_prompt = PromptTemplate(
my_qa_prompt_template, prompt_type="QUESTION_ANSWER"
)
query_engine = RetrieverQueryEngine.from_args(
nl_sql_retriever,
streaming=True,
text_qa_template=my_qa_prompt,
)
response = query_engine.query("陈经纶招多少?")
response.print_response_stream()
八、性能优化与安全考量
在实际生产环境中,仅靠上述代码是不够的,还需要考虑性能和安全性。
1. 上下文窗口管理
当数据库 schema 非常复杂时,直接将所有 DDL 放入 Prompt 会导致 Token 耗尽。建议采取以下策略:
- Schema Linking:只将与当前问题相关的表和字段加入上下文。
- 摘要化:对表注释进行压缩,保留关键语义。
- 分层检索:先检索元数据,再决定加载哪些 DDL。
2. 模型选择策略
- 本地模型:成本低,隐私性好,但能力有限。Qwen2-7B 等模型在简单查询上表现尚可,但在复杂多表关联上容易出错。
- 云端模型:GPT-4、Qwen-Turbo 等高级模型在 SQL 生成准确率上显著优于小参数模型。建议混合使用:简单查询用本地,复杂查询路由到云端。
3. 安全性防护
Text-to-SQL 最大的风险在于 SQL 注入。虽然 LlamaIndex 会生成 SQL,但必须确保:
- 只读权限:数据库账号仅授予 SELECT 权限,严禁 DELETE/UPDATE/DROP。
- 参数化查询:尽量使用 ORM 或参数化接口,避免字符串拼接。
- 异常捕获:对 SQL 执行过程中的语法错误或超时进行捕获,防止服务崩溃。
九、完整代码示例整合
为了方便复用,以下是一个整合了上述功能的简化版脚本结构:
import os
from sqlalchemy import create_engine, MetaData, Table, Column, String, Integer, insert
from llama_index.core import Settings
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.ollama import OllamaEmbedding
from llama_index.core.sql_database.interface import SQLDatabase
from llama_index.core.retrievers import NLSQLRetriever
from llama_index.core.query_engine import RetrieverQueryEngine
from llama_index.core.prompts import PromptTemplate
def setup_environment():
# 配置 LLM
Settings.llm = OpenAILike(
model="qwen2",
api_base="http://localhost:11434/v1",
api_key="ollama",
is_chat_model=True,
temperature=0.1
)
Settings.embed_model = OllamaEmbedding(
model_name="bge-large-zh-v1.5",
base_url="http://localhost:11434"
)
def init_db():
engine = create_engine("sqlite:///:memory:")
metadata_obj = MetaData()
table_name = "school_info"
school_info_table = Table(
table_name, metadata_obj,
Column("school_name", String(200), primary_key=True),
Column("students_enrolled", Integer, nullable=False),
)
metadata_obj.create_all(engine)
rows = [
{"school_name": "北京市第八十中学", "students_enrolled": 260},
{"school_name": "北京市陈经纶中学", "students_enrolled": 279},
]
for row in rows:
stmt = insert(school_info_table).values(**row)
with engine.begin() as connection:
connection.execute(stmt)
return SQLDatabase(engine)
def main():
setup_environment()
db = init_db()
retriever = NLSQLRetriever(db, tables=["school_info"])
engine = RetrieverQueryEngine.from_args(retriever, streaming=True)
query = "招生最多的是哪个学校?"
response = engine.query(query)
print(f"Query: {query}")
response.print_response_stream()
if __name__ == "__main__":
main()
十、总结与展望
本文演示了如何使用大模型将自然语言转换为 SQL,并将查询结果以自然语言形式返回。主要结论如下:
- 技术可行性:利用 LlamaIndex 可以较快地搭建 Text-to-SQL 原型,支持流式输出和基础模糊查询。
- 模型依赖性:得到预期结果高度依赖大模型的能力。GPT-4 或 Qwen 高级模型效果最好,本地小模型(如 7B)在复杂场景下可能需要微调或配合更强的云端模型。
- 提示词工程:通过定制 Prompt 模板,可以有效改善模糊查询和输出格式的控制。
- 未来方向:随着 Agent 技术的发展,未来的 NL2SQL 系统将具备自我纠错能力,能够自动修正错误的 SQL 并重新执行,进一步提高准确率。
在实际落地时,建议采用混合架构,结合本地模型的隐私优势和云端模型的高智能优势,并严格做好数据库权限隔离,以确保系统的安全稳定运行。


