手撸 Text2SQL 应用
Posted on Sun 01 June 2025 in Journal
Abstract | 手撸 Text2SQL 应用 |
---|---|
Authors | Walter Fan |
Category | learning note |
Status | v1.0 |
Updated | 2025-06-01 |
License | CC-BY-NC-ND 4.0 |
端午假期在家无事, 闲着也是闲着, 自己动手写一个 Text2SQL 生成器, 通过 Langchain 实现起来也很容易
只要我们有了 DB Schema, 加上大模型, 写 SQL 不再麻烦, 直接用自然语言描述你的要求, 直接就能生成所需的 SQL 以及相关的 MyBatis 语句.
1. 程序整体架构
这个程序是一个基于RAG技术的Text-to-SQL生成系统,主要包含以下组件:
Text2SQLGenerator
├── 环境配置 (SSL/HTTP设置)
├── 核心组件
│ ├── LLM (ChatOpenAI)
│ ├── 向量数据库 (Chroma)
│ ├── 嵌入模型 (HuggingFaceEmbeddings)
│ └── SQLite对话历史存储
└── 业务流程
├── 数据库模式提取
├── 向量索引构建
├── 检索增强生成
└── 对话历史管理
2. RAG技术详解
RAG的实现体现在以下关键流程中:
a) 检索阶段 (Retrieval)
def _get_vector_store(self, mysql_url: str):
# 从MySQL数据库提取模式(Schema)
schema = self._get_mysql_schema(mysql_url)
# 将模式文本分割为文档
docs = self.text_splitter.create_documents([schema])
# 构建向量存储
return Chroma.from_documents(
docs,
self.embedding,
collection_name="schema",
persist_directory=self.PERSIST_DIRECTORY
)
关键技术点:
- 使用HuggingFace的bge-small-en-v1.5
模型生成嵌入向量
- 将数据库模式(DDL语句)转换为向量表示
- 通过ChromaDB建立可持久化的向量索引
b) 增强阶段 (Augmentation)
# 获取相关数据库模式片段
docs = retriever.get_relevant_documents(request.query)
context = "\n---\n".join(doc.page_content for doc in docs)
# 构建包含以下内容的提示词:
prompt = """
Previous conversation: {history}
Database Schema: {context}
Question: {question}
"""
增强逻辑: 1. 根据用户 query 检索最相关的数据库模式片段 2. 将检索结果与对话历史一起注入提示词 3. 提供明确的JSON响应格式要求
c) 生成阶段 (Generation)
result_msg = self.llm.invoke(prompt)
result_json = self._extract_json(result_msg.content)
生成特点: - 使用ChatOpenAI作为生成模型 - 强制要求JSON格式输出 - 包含SQL和MyBatis两种输出形式
3. 关键技术组件
a) 分层检索架构
用户问题 → 向量相似度检索 → 数据库模式片段 → 提示词构建 → LLM生成
b) 动态上下文管理
- 短期记忆:通过SQLite保存对话历史
- 长期记忆:向量化的数据库模式知识
- 会话隔离:基于session_id的对话历史检索
c) 异常处理机制
def _extract_json(content: str):
# 尝试多种JSON提取方式:
# 1. 直接解析
# 2. 提取```json代码块
# 3. 提取{...}模式
# 确保非结构化响应也能被处理
4. 工作流程示例
对于查询:"how many applications are there in the system?"
- 检索阶段:
- 从向量库中找到
application
表的相关模式 -
例如:
CREATE TABLE application (id INT, name VARCHAR(255), status VARCHAR(50))
-
提示词构建: ```sql Database Schema: CREATE TABLE application ( id INT, name VARCHAR(255), status VARCHAR(50) )
Question: how many applications are there in the system? ```
- 生成输出:
json { "sql": "SELECT COUNT(*) FROM application", "mybatis": "<select id='countApplications' resultType='int'>...</select>" }
5. 小结
- 知识更新便捷:
- 只需重新导入数据库模式即可更新知识库
-
无需重新训练模型
-
多轮对话支持:
- 通过session_id维持会话上下文
-
历史问答自动注入后续提示
-
混合式知识管理:
- 结构化知识:数据库模式
- 非结构化知识:LLM的通用知识
-
会话知识:对话历史
-
生产就绪特性:
- 持久化存储向量索引和对话记录
- 完善的错误处理和日志记录
这样两百多行代码也就实现用 RAG 将传统数据库知识与大语言模型能力相结合,轻松地将自然语言转换为了 SQL。
# python text2sql_demo_2.py "how many applications in the system"
Results:
SQL: SELECT COUNT(DISTINCT id) FROM application;
MyBatis: <select id='countApplications' resultMap='stringResultMap'>
SELECT COUNT(DISTINCT id) AS count
FROM application
</select>
完整代码参见 https://github.com/walterfan/lazy-rabbit-agent/blob/master/example/text2sql_demo_2.py
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。