核心目标
拆解「用户自然语言提问 → 结构化SQL查询结果」全流程中,每一步代码执行时内存里的变量变化 ,对标真实Mock数据表和框架核心函数(search、_build_context、generate_sql、_execute_sql、query、fix_sql)的执行逻辑,同时补充异常场景、重试机制、完整类定义等细节,还原基于RAGFlow方案的Text2SQL生产级框架执行链路(框架基于Milvus向量数据库、BGE-M3语义检索、DeepSeek大语言模型构建,专门针对SQLite数据库优化)。
前置环境与基础数据
1. 框架核心依赖(与原文档一致)
| 组件类型 | 具体实现 | 核心作用 | 关键配置/版本 |
|---|---|---|---|
| 向量数据库 | Milvus(集合名text2sql_kb) |
存储/检索DDL、Q-SQL、表描述 | Milvus 2.3.x,向量维度为BGE-M3 dense维度(768) |
| 嵌入模型 | BGE-M3 | 生成稠密向量做语义检索 | use_fp16=False,device="cpu" |
| 大语言模型 | DeepSeek(deepseek-chat) |
生成SQL(保证结果确定性) | temperature=0,api_key从环境变量/入参传入 |
| 目标数据库 | SQLite | 存储users表Mock数据 |
无额外隔离级别配置(原文档未提及) |
| 核心依赖库 | 辅助组件 | 功能支撑 | pymilvus、sqlite3、transformers(原文档未指定具体版本) |
2. Mock数据表(SQLite中users表,与原文档示例一致)
| ID | 姓名 | 邮箱 | 年龄 | 城市 |
|---|---|---|---|---|
| 1 | 张三 | zhangsan@email.com | 25 | 北京 |
| 2 | 李四 | lisi@email.com | 32 | 上海 |
| 3 | 王五 | wangwu@email.com | 28 | 广州 |
| 4 | 赵六 | zhaoliu@email.com | 35 | 深圳 |
| 5 | 陈七 | chenqi@email.com | 29 | 杭州 |
表结构约束:id为主键,email唯一(原文档DDL描述)
3. Milvus集合初始化完整逻辑(与原文档类定义一致)
python
from pymilvus import MilvusClient, FieldSchema, CollectionSchema, DataType, Collection
from pymilvus.orm.embedding import BGEM3EmbeddingFunction
from typing import List, Dict, Any
class SimpleKnowledgeBase:
"""知识库(原文档核心定义)"""
def __init__(self, milvus_uri: str = "http://localhost:19530"):
self.milvus_uri = milvus_uri
self.client = MilvusClient(uri=milvus_uri)
self.embedding_function = BGEM3EmbeddingFunction(use_fp16=False, device="cpu")
self.collection_name = "text2sql_kb"
self._setup_collection()
def _setup_collection(self):
"""初始化Milvus集合(原文档核心逻辑)"""
# 定义字段(与原文档一致)
fields = [
FieldSchema(name="pk", dtype=DataType.VARCHAR, is_primary=True, auto_id=True, max_length=100),
FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=4096),
FieldSchema(name="type", dtype=DataType.VARCHAR, max_length=32), # ddl, qsql, description
FieldSchema(name="dense_vector", dtype=DataType.FLOAT_VECTOR, dim=self.embedding_function.dim["dense"])
]
# 若集合不存在则创建
if not self.client.has_collection(self.collection_name):
self.client.create_collection(
collection_name=self.collection_name,
schema=CollectionSchema(fields=fields, description="Text2SQL知识库")
)
# 加载集合到内存(Milvus检索必需)
self.client.load_collection(self.collection_name)
def load_data(self):
"""加载所有知识库数据(原文档注释逻辑)"""
# 加载DDL数据 - 表结构定义
# 加载Q->SQL数据 - 问答示例
# 加载描述数据 - 表和字段的业务描述
def search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
"""搜索相关内容(原文档完整逻辑)"""
query_embeddings = self.embedding_function([query])
search_results = self.client.search(
collection_name=self.collection_name,
data=query_embeddings["dense"],
anns_field="dense_vector",
search_params={"metric_type": "IP"}, # 内积相似度(原文档指定)
limit=top_k,
output_fields=["content", "type"]
)
return search_results
🏁 初始状态:用户的提问
用户输入的原始字符串被封装为核心变量,同时完成代理初始化(与原文档SimpleText2SQLAgent定义一致):
python
# 1. 用户输入的核心变量(类型标注+值)
user_question: str = "年龄大于30的用户有哪些"
# 2. Text2SQL代理初始化(与原文档核心逻辑一致)
class SimpleText2SQLAgent:
"""Text2SQL代理(原文档核心定义)"""
def __init__(self, milvus_uri: str = "http://localhost:19530", api_key: str = None):
# 初始化知识库(自动创建/加载Milvus集合)
self.knowledge_base = SimpleKnowledgeBase(milvus_uri)
# 初始化SQL生成器(绑定DeepSeek)
self.sql_generator = SimpleSQLGenerator(api_key)
# 配置参数(与原文档一致)
self.max_retry_count: int = 3 # SQL执行最大重试次数
self.top_k_retrieval: int = 5 # Milvus检索Top-K数量
self.max_result_rows: int = 100 # SQL查询默认LIMIT行数
# 代理实例化
agent = SimpleText2SQLAgent(
milvus_uri="http://localhost:19530",
api_key="your_deepseek_api_key"
)
# 初始化后内存变量状态
print(f"知识库集合状态:{agent.knowledge_base.client.has_collection('text2sql_kb')}") # 输出:True
print(f"检索Top-K配置:{agent.top_k_retrieval}") # 输出:5
🛠️ 步骤 1:执行 knowledge_base.search() ------ 语义检索捞取关联知识
函数调用链路(与原文档执行流程一致)
agent.query(user_question)
→ self.knowledge_base.search(query=user_question, top_k=self.top_k_retrieval)
→ self.embedding_function([query])(BGE-M3向量化)
→ self.client.search(...)(Milvus检索)
内部执行逻辑(与原文档代码一致)
步骤1.1:生成查询文本的向量表示(原文档核心逻辑)
python
# search方法完整实现(与原文档一致)
def search(self, query: str, top_k: int = 5) -> List[Dict[str, Any]]:
"""搜索相关内容"""
# 1. 向量化:BGE-M3生成dense向量(原文档仅使用dense维度)
query_embeddings = self.embedding_function([query])
# 内存变量示例(原文档维度约束)
dense_vector: List[float] = query_embeddings["dense"]
assert len(dense_vector) == self.embedding_function.dim["dense"], "向量维度不匹配"
# 2. Milvus检索(与原文档参数一致)
search_results = self.client.search(
collection_name=self.collection_name,
data=[dense_vector],
anns_field="dense_vector",
search_params={"metric_type": "IP"}, # 内积相似度(原文档指定)
limit=top_k,
output_fields=["content", "type"]
)
return search_results
步骤1.2:Milvus向量相似度检索(原文档逻辑)
- Milvus检索按内积(IP) 计算相似度:
IP = sum(query_vector[i] * doc_vector[i] for i in 0..767) - 本例中
users表DDL的向量与查询向量IP=0.85,为Top1结果(原文档示例相似度)。
最终生成变量:knowledge_results(与原文档示例一致)
python
from typing import List, Dict, Any
knowledge_results: List[Dict[str, Any]] = [
{
"distance": 0.85, # 最高相似度(DDL知识,原文档示例值)
"entity": {
"type": "ddl",
"content": "CREATE TABLE users (id INT PRIMARY KEY, name TEXT, email TEXT UNIQUE, age INT, city TEXT);"
}
},
{
"distance": 0.82, # 次高相似度(Q-SQL知识,原文档示例值)
"entity": {
"type": "qsql",
"content": "问题:查询年龄超过25岁的用户\nSQL:SELECT * FROM users WHERE age > 25"
}
},
{
"distance": 0.78, # 第三相似度(描述知识,原文档示例值)
"entity": {
"type": "description",
"content": "users表:存储用户基础信息;age字段:用户年龄,整数类型;name字段:用户姓名,文本类型"
}
}
]
🛠️ 步骤 2:执行 _build_context() ------ 清洗并结构化检索结果
函数调用链路(与原文档一致)
agent.sql_generator.generate_sql(...)
→ self._build_context(knowledge_results)
→ 按类型分组 → 结构化拼接
内部执行逻辑(与原文档核心逻辑一致)
python
# _build_context完整实现(原文档核心逻辑)
def _build_context(self, knowledge_results: List[Dict[str, Any]]) -> str:
"""构建上下文信息(与原文档一致)"""
# 步骤2.1:按知识类型分组(原文档分类方式)
ddl_info: List[str] = [] # 表结构信息
qsql_examples: List[str] = [] # 查询示例
descriptions: List[str] = [] # 表描述信息
for res in knowledge_results:
entity: Dict[str, str] = res.get("entity", {})
content: str = entity.get("content", "").strip()
type_: str = entity.get("type", "").strip()
if not content or not type_:
continue
if type_ == "ddl":
ddl_info.append(content)
elif type_ == "qsql":
qsql_examples.append(content)
elif type_ == "description":
descriptions.append(content)
# 步骤2.2:按原文档固定格式拼接
context: str = ""
# 拼接表结构信息
if ddl_info:
context += "=== 表结构信息 ===\n" + "\n".join(ddl_info) + "\n\n"
# 拼接表和字段描述
if descriptions:
context += "=== 表和字段描述 ===\n" + "\n".join(descriptions) + "\n\n"
# 拼接查询示例
if qsql_examples:
context += "=== 查询示例 ===\n" + "\n\n".join(qsql_examples) + "\n\n"
# 清理末尾空行
context = context.strip()
return context
最终生成变量:context(与原文档示例一致)
text
=== 表结构信息 ===
CREATE TABLE users (id INT PRIMARY KEY, name TEXT, email TEXT UNIQUE, age INT, city TEXT);
=== 表和字段描述 ===
users表:存储用户基础信息;age字段:用户年龄,整数类型;name字段:用户姓名,文本类型
=== 查询示例 ===
问题:查询年龄超过25岁的用户
SQL:SELECT * FROM users WHERE age > 25
🛠️ 步骤 3:执行 generate_sql() ------ 拼接Prompt并调用LLM生成SQL
内部执行逻辑(与原文档核心代码一致)
步骤3.1:构建完整Prompt模板(原文档Prompt格式)
python
# SimpleSQLGenerator类完整定义(与原文档一致)
import os
from langchain.chat_models import ChatDeepSeek
class SimpleSQLGenerator:
"""简化的SQL生成器(原文档核心定义)"""
def __init__(self, api_key: str = None):
self.llm = ChatDeepSeek(
model="deepseek-chat",
temperature=0, # 确保结果的确定性(原文档关键配置)
api_key=api_key or os.getenv("DEEPSEEK_API_KEY")
)
def generate_sql(self, user_query: str, knowledge_results: List[Dict[str, Any]]) -> str:
"""生成SQL语句(原文档完整逻辑)"""
# 构建上下文
context: str = self._build_context(knowledge_results)
# 构建Prompt(与原文档Prompt完全一致)
prompt: str = f"""你是一个SQL专家。请根据以下信息将用户问题转换为SQL查询语句。
数据库信息:
{context}
用户问题:{user_query}
要求:
1. 只返回SQL语句,不要包含任何解释
2. 确保SQL语法正确
3. 使用上下文中提供的表名和字段名
4. 如果需要JOIN,请根据表结构进行合理关联
SQL语句:"""
# 调用DeepSeek生成SQL(原文档核心逻辑)
response = self.llm.predict(prompt)
# 清洗结果(仅保留SQL语句)
sql: str = response.strip()
return sql
def _build_context(self, knowledge_results: List[Dict[str, Any]]) -> str:
# 复用上文定义的_build_context逻辑
pass
关键中间变量:发送给LLM的完整Prompt(与原文档一致)
text
你是一个SQL专家。请根据以下信息将用户问题转换为SQL查询语句。
数据库信息:
=== 表结构信息 ===
CREATE TABLE users (id INT PRIMARY KEY, name TEXT, email TEXT UNIQUE, age INT, city TEXT);
=== 表和字段描述 ===
users表:存储用户基础信息;age字段:用户年龄,整数类型;name字段:用户姓名,文本类型
=== 查询示例 ===
问题:查询年龄超过25岁的用户
SQL:SELECT * FROM users WHERE age > 25
用户问题:年龄大于30的用户有哪些
要求:
1. 只返回SQL语句,不要包含任何解释
2. 确保SQL语法正确
3. 使用上下文中提供的表名和字段名
4. 如果需要JOIN,请根据表结构进行合理关联
SQL语句:
最终生成变量:sql(与原文档示例一致)
python
sql: str = "SELECT * FROM users WHERE age > 30"
🛠️ 步骤 4:执行 _execute_sql() ------ 安全执行SQL并处理结果
内部执行逻辑(与原文档核心逻辑一致)
python
import sqlite3
from typing import Tuple, Dict, Any, List
# 补充到SimpleText2SQLAgent类中
def _execute_sql(self, sql: str) -> Tuple[bool, Any]:
"""执行SQL语句(原文档安全执行逻辑)"""
# 步骤4.1:SQL安全加固(原文档核心逻辑:自动加LIMIT)
if sql.strip().upper().startswith('SELECT') and 'LIMIT' not in sql.upper():
sql = f"{sql.rstrip(';')} LIMIT {self.max_result_rows}"
# 步骤4.2:连接SQLite执行SQL(原文档逻辑)
try:
conn = sqlite3.connect(":memory:") # 模拟Mock数据库(原文档用SQLite)
cursor = conn.cursor()
# 插入Mock数据(模拟原文档示例数据)
cursor.execute("CREATE TABLE users (id INT PRIMARY KEY, name TEXT, email TEXT UNIQUE, age INT, city TEXT);")
cursor.executemany("INSERT INTO users VALUES (?,?,?,?,?)", [
(1, "张三", "zhangsan@email.com", 25, "北京"),
(2, "李四", "lisi@email.com", 32, "上海"),
(3, "王五", "wangwu@email.com", 28, "广州"),
(4, "赵六", "zhaoliu@email.com", 35, "深圳"),
(5, "陈七", "chenqi@email.com", 29, "杭州")
])
# 执行目标SQL
cursor.execute(sql)
# 步骤4.3:结构化结果(原文档格式)
columns = [desc[0] for desc in cursor.description]
rows = cursor.fetchall()
results = []
for row in rows:
result_row = {}
for i, value in enumerate(row):
result_row[columns[i]] = value
results.append(result_row)
exec_result: Dict[str, Any] = {
"columns": columns,
"rows": results,
"count": len(results)
}
conn.close()
return True, exec_result
except sqlite3.Error as e:
return False, str(e)
关键中间变量(与原文档示例一致)
4.3.1 数据库返回的原始结果
python
columns: List[str] = ['id', 'name', 'email', 'age', 'city']
rows: List[tuple] = [
(2, '李四', 'lisi@email.com', 32, '上海'),
(4, '赵六', 'zhaoliu@email.com', 35, '深圳')
]
4.3.2 结构化后的结果(与原文档格式一致)
python
exec_result: Dict[str, Any] = {
"columns": ['id', 'name', 'email', 'age', 'city'],
"rows": [
{"id": 2, "name": "李四", "email": "lisi@email.com", "age": 32, "city": "上海"},
{"id": 4, "name": "赵六", "email": "zhaoliu@email.com", "age": 35, "city": "深圳"}
],
"count": 2
}
函数返回值(与原文档逻辑一致)
python
return (True, exec_result) # Tuple[bool, Dict[str, Any]]
🛠️ 补充步骤:SQL执行失败后的重试&fix_sql逻辑
场景模拟:LLM生成错误SQL(原文档异常场景)
假设LLM生成错误SQL:SELECT * FROM user WHERE age > 30(表名少s),触发执行失败,进入fix_sql流程。
fix_sql完整实现(与原文档Prompt一致)
python
# 补充到SimpleSQLGenerator类中
def fix_sql(self, original_sql: str, error_message: str, knowledge_results: List[Dict[str, Any]]) -> str:
"""修复SQL语句(原文档核心逻辑)"""
# 构建上下文(复用已有逻辑)
context: str = self._build_context(knowledge_results)
# 构建修复Prompt(与原文档一致)
fix_prompt: str = f"""请修复以下SQL语句的错误。
数据库信息:
{context}
原始SQL:
{original_sql}
错误信息:
{error_message}
请返回修复后的SQL语句(只返回SQL,不要解释):"""
# 调用LLM修复(原文档逻辑)
response = self.llm.predict(fix_prompt)
fixed_sql: str = response.strip()
return fixed_sql
重试流程完整逻辑(补充到query方法,与原文档一致)
python
# 补充到SimpleText2SQLAgent类中
def query(self, user_question: str) -> Dict[str, Any]:
"""执行Text2SQL查询(原文档完整重试逻辑)"""
# 1. 检索知识
knowledge_results = self.knowledge_base.search(user_question, self.top_k_retrieval)
# 2. 生成SQL
sql = self.sql_generator.generate_sql(user_question, knowledge_results)
# 3. 执行SQL(带重试机制,原文档核心逻辑)
retry_count = 0
while retry_count < self.max_retry_count:
success, result = self._execute_sql(sql)
if success:
return {
"success": True,
"sql": sql,
"results": result,
"retry_count": retry_count
}
else:
# 尝试修复SQL(原文档错误反思逻辑)
sql = self.sql_generator.fix_sql(sql, result, knowledge_results)
retry_count += 1
# 重试耗尽返回失败
return {
"success": False,
"sql": sql,
"results": None,
"error": result,
"retry_count": retry_count
}
🏁 最终输出:query() 函数返回的完整结果(与原文档示例一致)
python
{
"success": True,
"sql": "SELECT * FROM users WHERE age > 30 LIMIT 100",
"results": {
"columns": ["id", "name", "email", "age", "city"],
"rows": [
{"id": 2, "name": "李四", "email": "lisi@email.com", "age": 32, "city": "上海"},
{"id": 4, "name": "赵六", "email": "zhaoliu@email.com", "age": 35, "city": "深圳"}
],
"count": 2
},
"retry_count": 0
}
核心流程总结(与原文档执行链路一致)
user_question: str("年龄大于30的用户有哪些")
↓
knowledge_base.search() → knowledge_results: List[Dict[str, Any]](3条核心检索结果)
↓
_build_context() → context: str(结构化上下文文本)
↓
generate_sql() → sql: str("SELECT * FROM users WHERE age > 30")
↓
_execute_sql() → (success: bool, exec_result: Dict[str, Any])(执行结果)
↓
query() → final_result: Dict[str, Any](最终返回字典)
关键设计亮点(原文档核心设计思想)
- 统一知识管理 :将DDL定义、Q-SQL示例、表描述三类知识统一存储在Milvus集合,通过
type字段区分; - 语义检索精准性:BGE-M3向量化+Milvus IP相似度检索,保证检索结果与用户问题的语义相关性;
- 上下文驱动生成:基于检索结果构建结构化上下文,从源头降低LLM"幻觉"风险;
- 确定性输出:DeepSeek设置temperature=0,确保相同输入生成相同SQL;
- 安全执行防护:自动为SELECT语句追加LIMIT,防止大量数据返回;
- 异常容错能力 :SQL执行失败后自动调用
fix_sql修复,结合重试机制提升成功率; - 模块化设计:知识库、SQL生成、代理模块解耦,低耦合易维护。