Text2SQL 完整流程模拟详细笔记

核心目标

拆解「用户自然语言提问 → 结构化SQL查询结果」全流程中,每一步代码执行时内存里的变量变化 ,对标真实Mock数据表和框架核心函数(search_build_contextgenerate_sql_execute_sqlqueryfix_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](最终返回字典)

关键设计亮点(原文档核心设计思想)

  1. 统一知识管理 :将DDL定义、Q-SQL示例、表描述三类知识统一存储在Milvus集合,通过type字段区分;
  2. 语义检索精准性:BGE-M3向量化+Milvus IP相似度检索,保证检索结果与用户问题的语义相关性;
  3. 上下文驱动生成:基于检索结果构建结构化上下文,从源头降低LLM"幻觉"风险;
  4. 确定性输出:DeepSeek设置temperature=0,确保相同输入生成相同SQL;
  5. 安全执行防护:自动为SELECT语句追加LIMIT,防止大量数据返回;
  6. 异常容错能力 :SQL执行失败后自动调用fix_sql修复,结合重试机制提升成功率;
  7. 模块化设计:知识库、SQL生成、代理模块解耦,低耦合易维护。
相关推荐
源分享6 小时前
Java线程同步的多种实现方法(非常详细)
java·开发语言·jvm
闪闪发亮的小星星7 小时前
高斯光以及高斯光公式解释
笔记
JAVA9657 小时前
JAVA面试-JVM篇 03-JVM运行时数据区哪些是线程私有的哪些是共享的
java·jvm·面试
cqbzcsq8 小时前
CellFlow虚拟细胞论文阅读
论文阅读·人工智能·笔记·学习·生物信息
曹牧8 小时前
Oracle EXPLAIN PLAN
数据库·oracle
贤时间8 小时前
codex 助力oracle ebs 开发
数据库·oracle
秉承初心8 小时前
PostgreSQL 数据性能瓶颈突破实战
数据库·postgresql·oracle
阿米亚波9 小时前
【Windows】QEMU 启动 openEuler aarch64/arm64 架构系统 + 离线软件源
linux·windows·经验分享·笔记·架构·arm
自传.9 小时前
尚硅谷 Vibe Coding|第三章(1) Claude Code深度使用与进阶技巧 学习笔记
笔记·学习·尚硅谷·vibecoding
.千余10 小时前
【C++】模板进阶全解:非类型参数|全特化|偏特化|分离编译完全指南
开发语言·c++·笔记·学习·其他