基于大模型的自然语言数据库查询实现指南
使用大模型(LLM)通过自然语言生成 SQL 语句,从结构化数据库中获取结果,是目前大模型与数据交互的主流形式之一。这种技术通常被称为 Text-to-SQL 或 NL2SQL(Natural Language to SQL)。它极大地降低了非技术人员访问数据的门槛,使得业务人员可以直接通过对话方式查询数据。
本文详细介绍了使用大模型通过自然语言查询数据库的技术实现方案。内容涵盖环境搭建、SQLAlchemy 数据建模、LlamaIndex 配置、基础查询与流式输出实现、模糊查询的提示词优化以及安全与性能考量。通过具体代码示例展示了如何集成本地 Ollama 模型或云端 API,解决了上下文限制、SQL 生成准确性等问题,并为实际生产环境提供了完整的参考架构和安全建议。

使用大模型(LLM)通过自然语言生成 SQL 语句,从结构化数据库中获取结果,是目前大模型与数据交互的主流形式之一。这种技术通常被称为 Text-to-SQL 或 NL2SQL(Natural Language to SQL)。它极大地降低了非技术人员访问数据的门槛,使得业务人员可以直接通过对话方式查询数据。
本文将详细介绍如何使用 Python 生态中的 LlamaIndex、SQLAlchemy 以及本地或云端大模型,构建一个支持自然语言查询数据库的系统。我们将涵盖环境搭建、数据准备、基础查询、流式输出、模糊查询优化以及安全注意事项等完整流程。
Text-to-SQL 的基本流程如下:
以下是一个简单的示例场景:存储朝阳区高中学校招生信息的数据库,用户提问 陈经纶招多少人?,系统大致处理步骤为:
select * from school_info where school_name like '%陈经纶%'。北京市陈经纶中学招收的学生人数为 279 名。在开始之前,需要准备好开发环境。推荐使用 Jupyter Notebook 或 JupyterLab 进行交互式开发。
我们需要安装以下核心库:
pip install llama-index sqlalchemy pandas ollama openai
llama-index: 用于连接 LLM 和外部数据源的核心框架。sqlalchemy: ORM 工具,用于定义数据库结构和操作记录。pandas: 用于数据处理和展示。ollama: 用于运行本地大模型。openai: 兼容 OpenAI API 格式的客户端,用于调用云端或本地兼容接口。本文演示同时支持本地模型(通过 Ollama)和云端模型(通过 One-API 或类似网关)。
我们使用 SQLAlchemy 在内存 SQLite 数据库中创建示例表和相关记录。SQLite 轻量且无需额外服务器,适合演示。
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)
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 查看数据是否正确写入:
import pandas as pd
with engine.connect() as conn:
df = pd.read_sql("SELECT * FROM school_info", conn)
print(df)
在使用 LlamaIndex 进行查询前,需要设置全局的 LLM 和 Embedding 模型。这样后续方法调用时无需重复传递参数。
这里以 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 查询引擎。
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 名学生。'
这种方式虽然简单,但存在明显不足:
为了提升用户体验,需要使用 LlamaIndex 底层的检索 API (Retriever) 来构建支持流式的查询引擎。
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()
启用流式后,用户可以看到文字逐字生成,大大减少了等待焦虑感。这对于长文本回答尤为重要。
默认情况下,Text-to-SQL 对模糊匹配的支持较弱,往往要求精确匹配。如果需要支持模糊查询(如 LIKE '%关键词%'),需要调整 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 名学生。
除了生成 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()
在实际生产环境中,仅靠上述代码是不够的,还需要考虑性能和安全性。
当数据库 schema 非常复杂时,直接将所有 DDL 放入 Prompt 会导致 Token 耗尽。建议采取以下策略:
Text-to-SQL 最大的风险在于 SQL 注入。虽然 LlamaIndex 会生成 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,并将查询结果以自然语言形式返回。主要结论如下:
在实际落地时,建议采用混合架构,结合本地模型的隐私优势和云端模型的高智能优势,并严格做好数据库权限隔离,以确保系统的安全稳定运行。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online