使用大模型进行自然语言查询数据库
利用大语言模型(LLM)通过自然语言生成 SQL 语句,从结构化数据库中获取结果,是目前大模型与数据交互的主流形式之一。这种技术通常被称为 NL2SQL(Natural Language to SQL)。
核心流程
以查询朝阳区高中学校招生信息为例,当用户提问 陈经纶招多少人? 时,系统处理的大致步骤如下:
- 上下文注入:将数据库的 DDL(建表语句)加入对话上下文,使大模型感知表结构。
- SQL 生成:大模型将提示词转化为 SQL 查询语句,例如
select * from school_info where school_name like '%陈经纶%'。 - 结果解释:大模型根据 SQL 查询结果,生成自然语言的回答,如
北京市陈经纶中学招收的学生人数为 279 名。
以下示例基于 Jupyter Notebook 环境实现,主要涉及 SQLAlchemy、LlamaIndex 以及本地或云端模型调用。
准备数据
首先使用 SQLAlchemy 在 SQLite 内存数据库中创建表结构和记录。
from sqlalchemy import (
create_engine,
MetaData,
Table,
Column,
String,
Integer,
select,
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)
# 插入学校信息记录
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)
数据可以通过 pandas 查询显示,确保数据已正确写入。
最基本的使用
需要配置 LLM 和嵌入模型,设置到 LlamaIndex 全局 Settings 中,这样后续方法无需重复设置参数。
from llama_index.core import Settings
from llama_index.llms.openai_like import OpenAILike
from llama_index.embeddings.ollama import OllamaEmbedding
# 设置 LLM (以本地 Ollama 为例)
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
)
# 设置 Embedding 模型
Settings.embed_model = OllamaEmbedding(
model_name="quentinz/bge-large-zh-v1.5",
base_url="http://localhost:11434",
ollama_additional_kwargs={"mirostat": 0}
)
执行基础查询:
from llama_index.core.sql_database 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)
# 输出示例:'招生最多的是北京工业大学附属中学,共有 418 名学生。'
局限性分析:
- 不支持对话流式输出,用户体验较卡顿。
- 当表数量较多时,可能受到对话上下文长度限制,导致关键元数据丢失。
支持流式输出回答
为了提升体验,需要使用 LlamaIndex 底层的检索 API 来实现流式响应。
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()
运行效果将逐字打印出答案,显著提升交互流畅度。
支持模糊查询
默认情况下,NL2SQL 对模糊匹配的支持较弱。直接询问 陈经纶招多少? 可能因无法精确匹配表结构而返回空结果。
优化方案
需要增加提示词约束,并依赖能力更强的 LLM 模型。本地小模型(如 Qwen 7B/14B)可能难以生成准确的模糊查询 SQL,建议尝试云端高级模型。
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://api.example.com/v1",
api_key="<YOUR_API_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}"
"查询关键字使用模糊查询,并且查询结果应包含关键字所属的列"
)
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 名学生。
定制回答格式
如果需要更严格的输出格式(例如必须显示全名),可以在 QA 阶段进一步定制提示词。
my_qa_prompt_template = (
"回答中要求使用学校的完整名称 (school_name)"
"不用再计算,给出的就是答案"
"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()
模型选择与性能评估
在实际测试中,不同模型的表现差异显著:
- GPT-4:效果最好,理解复杂语义能力强,但成本较高。
- 国内云端模型:Qwen 高级模型表现较好,适合中文场景。
- 本地模型:Qwen2:7B 等模型在检索阶段(NL2SQL)可能需要依赖更强的云端模型辅助,否则生成的 SQL 语法错误率较高。
初步结论:
- 如果采用 NL2SQL 方式,生产环境建议采用能力更高的云端模型,或者对本地模型进行垂直领域微调。
- 对于简单查询,本地模型配合精心设计的 Prompt 可降低成本。
安全与最佳实践
在使用大模型查询数据库时,需注意以下安全问题:
- 权限控制:确保 LLM 仅拥有只读权限,严禁授予 DROP 或 DELETE 权限。
- Prompt 注入:警惕用户输入中包含恶意 SQL 指令,需对输入内容进行清洗。
- 敏感数据脱敏:在将 Schema 发送给 LLM 前,应移除包含隐私信息的字段描述。
- 缓存机制:对相同的查询请求进行缓存,减少 Token 消耗并提高响应速度。
总结
本文演示了如何使用大模型将自然语言转换为 SQL,并将查询结果以自然语言形式反馈给用户。关键点包括:
- 依赖大模型能力和相关的提示词工程。
- 本地模型与云端模型各有优劣,需根据场景权衡。
- 流式输出和模糊查询需要通过底层 API 和 Prompt 优化来实现。
- 生产部署需重点关注安全性与成本控制。
通过合理配置 LlamaIndex 框架及选择合适的模型,开发者可以快速构建具备自然语言交互能力的数据库应用。

