摘要:SQL 很强,但也很烦。本文将带你实战构建一个基于 Python 和 DeepSeek 大模型的 Text-to-SQL 智能体。通过动态 Schema 注入和 Prompt 工程,让 LLM 充当“翻译官”,实现用自然语言直接查询本地数据库。
0. 引言:SQL 的痛与 AIGC 的药
在传统的后端开发中,CRUD(增删改查)占据了我们大量的时间。对于前端开发者、产品经理或数据分析师来说,SQL 更是那道难以逾越的“叹息之墙”——稍微忘记一个 JOIN 语法或者字段名拼错,查询就报错。
AIGC 时代的到来,正在重构数据库的交互方式。
如果我们能将 LLM(大语言模型)作为中间层,让它理解人类的自然语言(Natural Language),并根据数据库结构自动翻译成机器语言(SQL),那么数据库的操作门槛将无限趋近于零。这就是 Text-to-SQL。
今天,我们拒绝手写 SQL,用 Python 配合国产推理编程能力极强的 DeepSeek,手撸一个智能数据查询助手。
1. 技术选型:SQLite + DeepSeek
为什么选择这套组合?
-
SQLite:
- 轻量级:Python 原生内置,无需安装 MySQL/PostgreSQL 服务,一个 .db 文件就是整个数据库。
- 广泛应用:不要小看它,你的微信聊天记录、浏览器历史记录,底层都是 SQLite。它非常适合作为 AI Agent 的本地记忆体或原型验证。
-
DeepSeek (深度求索) :
- 代码能力强:DeepSeek-V3/R1 在代码生成和 SQL 逻辑推理上表现优异。
- 兼容性好:完全兼容 OpenAI SDK 协议,这意味着我们可以直接复用成熟的工具链。
2. 核心原理:Prompt Engineering for SQL
很多新手直接把问题扔给 LLM:“帮我查查工资最高的员工”。
LLM 会一脸懵逼:“你有哪些表?字段叫 salary 还是 wage?”
要实现精准的 Text-to-SQL,核心在于 Schema Injection (结构注入) 。
2.1 动态获取 Schema
我们需要让 Python 自动读取数据库的“元数据”,而不是手动把表结构复制给 AI。在 SQLite 中,我们可以使用 PRAGMA table_info(table_name) 来获取列名和类型。
2.2 Prompt 设计三要素
一个优秀的 SQL 生成 Prompt 必须包含:
- Role (角色) :你是一个 SQL 专家。
- Context (上下文) :这是我的数据库表结构 CREATE TABLE ...。
- Constraints (约束) :只输出 SQL,不要 Markdown,不要废话。
3. 实战代码解析
下面是完整的、经过 Bug 修正的 Python 代码。它实现了从“建表造数据”到“AI 生成 SQL”再到“执行查询”的完整闭环。
3.1 环境准备
Bash
# 确保安装了 OpenAI SDK(用于调用 DeepSeek)
!pip install openai
3.2 完整实现
Python
import sqlite3
from openai import OpenAI
# 配置 DeepSeek 客户端
# 注意:实际开发中请将 API Key 放入环境变量
client = OpenAI(
api_key='sk-xxxx', # 替换为你的 DeepSeek API Key
base_url='https://api.deepseek.com/v1',
)
def init_db(db_path="test.db"):
"""初始化数据库:建表并插入模拟数据"""
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 1. 每次运行前清理旧表,防止 'UNIQUE constraint failed' 报错
cursor.execute("DROP TABLE IF EXISTS employees")
# 2. 创建表结构
cursor.execute("""
CREATE TABLE employees(
id INTEGER PRIMARY KEY,
name TEXT,
department TEXT,
salary INTEGER
)
""")
# 3. 插入模拟数据
sample_data = [
(6, "程浩", "开发部", 32000),
(7, "张三", "销售部", 20000),
(8, "草威威", "开发部", 33000),
(9, "李四", "销售部", 15000)
]
cursor.executemany('INSERT INTO employees VALUES(?,?,?,?)', sample_data)
conn.commit()
print("数据库初始化完成,数据已注入。")
return conn
def get_table_schema(conn, table_name="employees"):
"""
核心逻辑:动态获取数据库表结构
PRAGMA table_info 返回格式: (cid, name, type, notnull, dflt_value, pk)
"""
cursor = conn.cursor()
# 注意:fetchall() 返回的是列表,原代码中的逗号会导致它变成元组
columns = cursor.execute(f"PRAGMA table_info({table_name})").fetchall()
# 将元组数据拼接成类似 CREATE TABLE 的定义字符串
# col[1] 是字段名, col[2] 是字段类型
schema_str = f"CREATE TABLE {table_name} (\n"
schema_str += ",\n".join([f" {col[1]} {col[2]}" for col in columns])
schema_str += "\n)"
return schema_str
def ask_deepseek_sql(query, schema):
"""构建 Prompt 并调用 LLM"""
prompt = f"""
你是一个 SQLite 数据库专家。请根据以下表结构,生成对应的 SQL 查询语句。
【表结构 Schema】:
{schema}
【用户问题】:
{query}
【严格约束】:
1. 仅输出 SQL 语句本身,不要输出 Markdown 格式(如 ```sql ... ```)。
2. 不要输出任何解释性文字。
"""
try:
response = client.chat.completions.create(
model="deepseek-chat", # 推荐使用 chat 模型,速度快且稳定
messages=[{"role": "user", "content": prompt}],
temperature=0 # 设置为 0,让输出尽可能确定
)
# 修复原代码中的 response.choice 属性错误
sql = response.choices[0].message.content.strip()
# 双重保险:清洗可能存在的 markdown 符号
return sql.replace('```sql', '').replace('```', '')
except Exception as e:
return f"API调用失败: {e}"
# --- 主程序运行 ---
# 1. 初始化
conn = init_db()
# 2. 获取 Schema 上下文
schema_context = get_table_schema(conn)
print(f"\n[Context] 当前表结构:\n{schema_context}")
# 3. 用户提问
user_question = "开发部部门员工的姓名和工资是多少?"
print(f"\n[User] 提问: {user_question}")
# 4. LLM 生成 SQL
generated_sql = ask_deepseek_sql(user_question, schema_context)
print(f"[AI] 生成 SQL: {generated_sql}")
# 5. 执行生成的 SQL
if generated_sql.upper().startswith("SELECT"):
cursor = conn.cursor()
try:
results = cursor.execute(generated_sql).fetchall()
print(f"\n[DB] 查询结果:")
for row in results:
print(row)
except sqlite3.Error as e:
print(f"SQL 执行错误: {e}")
else:
print("生成的语句不是查询语句,跳过执行。")
conn.close()
3.3 代码运行结果
Text
数据库初始化完成,数据已注入。
[Context] 当前表结构:
CREATE TABLE employees (
id INTEGER,
name TEXT,
department TEXT,
salary INTEGER
)
[User] 提问: 开发部部门员工的姓名和工资是多少?
[AI] 生成 SQL: SELECT name, salary FROM employees WHERE department = '开发部';
[DB] 查询结果:
('程浩', 32000)
('草威威', 33000)
4. 关键点总结
- Cursor 的复用:在 Python 中,cursor 是数据库操作的句柄。无论是 PRAGMA 获取结构,还是 EXECUTE 执行查询,都离不开它。
- Schema 的重要性:代码中的 get_table_schema 函数是连接 LLM 与 数据库的桥梁。如果没有这一步,DeepSeek 就算再聪明,也猜不到你的字段叫 department 还是 dept。
- 清洗输出:LLM 即使被要求“只输出 SQL”,有时也会礼貌地加上 Markdown 格式。在代码中加入 .replace('```', '') 是工程化落地的必要手段。
5. 展望
这就结束了吗?这只是开始。
目前的 Text-to-SQL 只是实现了“查询翻译”。未来的 Data Agent 将会更加强大:
- 分析能力:查询出数据后,直接调用 Python matplotlib 库画出图表。
- 多表关联:通过更复杂的 Prompt,处理 JOIN 等高阶逻辑。
- 安全校验:在执行 SQL 前增加一层校验,防止 DROP TABLE 等恶意操作。
DeepSeek + Python,正在让每一个普通开发者拥有操作数据的“上帝视角”。快去试试吧!